1
0
mirror of https://github.com/ilri/dspace-statistics-api.git synced 2025-05-10 15:16:02 +02:00

Compare commits

...

18 Commits
v5_x ... v1.2.0

Author SHA1 Message Date
5dd50ff998 CHANGELOG.md: Version 1.2.0
This version only works with DSpace 6+ where the internal item id-
entifiers are UUIDs instead of integers. Version 1.1.1 was the last
version to work with DSpace 4 and 5.
2020-03-02 11:34:58 +02:00
6704e7375f CHANGELOG.md: Add note about Python dependencies 2020-03-02 11:34:13 +02:00
37630d8dac CHANGELOG.md: Add note about DSpace 6+ UUIDs 2020-03-02 11:27:10 +02:00
0ef071a91d dspace_statistics_api: Use f-strings instead of format()
We had previously been avoiding the f-strings because we needed to
run on Python 3.5 and they were only available in Python 3.6+, but
now the black formatter requires Python 3.6 and all our systems are
running Python 3.6+ anyways.
2020-03-02 11:24:29 +02:00
9e7dd28156 dspace_statistics_api/app.py: Use parameterized SQL queries
This is a better way to run SQL queries because psycopg2 takes care
of the quoting for us.
2020-03-02 11:16:05 +02:00
60e6ea57b1 tests/test_api.py: Use UUID
DSpace 6+ uses a UUID for item identifiers instead of an integer so
we need to adapt our tests accordingly. The Python UUID object must
be cast to a string to use it elsewhere in the code.
2020-03-02 11:10:41 +02:00
5955868b9a dspace_statistics_api/app.py: Use UUID
DSpace 6+ uses a UUID for item identifiers instead of an integer so
we need to adapt our PostgreSQL queries to use those. Note that we
can no longer sort results in the "all items" endpoint by ID. Also,
we need to use parameterized psycopg2 queries instead of strings to
support queries with UUIDs properly. To use the Python UUID objects
elsewhere in the code we need to make sure that we cast them to str.
2020-03-02 11:06:48 +02:00
250fd8164f dspace_statistics_api/indexer.py: Use UUID
DSpace 6+ uses a UUID for item identifiers instead of an integer so
we need to update the PostgreSQL schema accordingly. Solr still re-
fers to them as "id" in its schema so we don't need to change anyt-
hing there.
2020-03-01 21:22:10 +02:00
82be1a4d00 Update requirements
Generated from pipenv with:

  $ pipenv lock -r > requirements.txt
  $ pipenv lock -r -d > requirements-dev.txt
2020-03-01 21:21:13 +02:00
0615064e3d Add pytest-clarity to pipenv
Makes pytest output easier to understand.
2020-03-01 21:19:28 +02:00
76be1b749a Run pipenv update 2020-03-01 21:13:32 +02:00
92146fe426 tests/test_api.py: Format with black 2019-12-14 12:39:58 +02:00
440b2f2dfa Pipfile.lock: Run pipenv update 2019-12-14 12:38:11 +02:00
67bc30ead0 Pipfile: Specify exact version of black
Black only releases pre-release versions, which causes issues with
pipenv. Instead of always running pipenv with "--pre" and potenti-
ally letting in some other pre-release versions for other depende-
ncies, I would rather specify the latest black version explicitly.

See: https://github.com/psf/black/issues/517
See: https://github.com/microsoft/vscode-python/issues/5171
2019-12-14 12:37:10 +02:00
142959acdb CHANGELOG.md: Unreleased changes 2019-11-27 12:56:39 +02:00
322f5a8db8 .travis.yml: Remove Python 3.5
black does not work with Python 3.5. It's not such a big deal, as
this is only required for running tests, not for running the app.
2019-11-27 12:55:34 +02:00
90dcaa6ec6 CHANGELOG.md: Fix typo 2019-11-27 12:47:07 +02:00
9aca827d69 Update requirements-dev.txt
Generated with pipenv:

    $ pipenv lock -r -d > requirements-dev.txt
2019-11-27 12:36:05 +02:00
10 changed files with 191 additions and 155 deletions

View File

@ -1,7 +1,6 @@
dist: bionic dist: bionic
language: python language: python
python: python:
- "3.5"
- "3.6" - "3.6"
- "3.7" - "3.7"
- "3.8" - "3.8"

View File

@ -4,13 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.2.0] - 2020-03-02
## Changed
- Remove Python 3.5 from TravisCI because black requires Python >= 3.6
- Adapt API for DSpace 6+ UUIDs
- This requires droping the statistics database and re-indexing
- Run pipenv update, bringing requests 2.23.0 and pytest 5.3.5
## [1.1.1] - 2019-11-27 ## [1.1.1] - 2019-11-27
### Added ### Added
- Configuration for automatic sorting of imports with isort - Configuration for automatic sorting of imports with isort
- Configuration for automatic code formatting with black - Configuration for automatic code formatting with black
### Updated ### Updated
- Run pipenv update, bringing psycogpg 2.8.3, requests 2.22.0, pytest 5.3.1, - Run pipenv update, bringing psycopg2 2.8.4, requests 2.22.0, pytest 5.3.1,
and gunicorn 20.0.4 and gunicorn 20.0.4
### Changed ### Changed

View File

@ -14,7 +14,8 @@ ipython = "*"
"flake8" = "*" "flake8" = "*"
pytest = "*" pytest = "*"
isort = "*" isort = "*"
black = "*" black = "==19.10b0"
pytest-clarity = "==0.3.0a0"
[requires] [requires]
python_version = "3.8" python_version = "3.8"

208
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "5e3f3e80bd92780ab66a80a83156001e50362a0d6195b1a304a15dc0c645c562" "sha256": "be968d3927117f9ac14b9a6f60d6147b2d57ce55f694f34ed6e53abcd2197823"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -18,10 +18,10 @@
"default": { "default": {
"certifi": { "certifi": {
"hashes": [ "hashes": [
"sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50", "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3",
"sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef" "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f"
], ],
"version": "==2019.9.11" "version": "==2019.11.28"
}, },
"chardet": { "chardet": {
"hashes": [ "hashes": [
@ -60,10 +60,10 @@
}, },
"idna": { "idna": {
"hashes": [ "hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
], ],
"version": "==2.8" "version": "==2.9"
}, },
"psycopg2-binary": { "psycopg2-binary": {
"hashes": [ "hashes": [
@ -105,18 +105,18 @@
}, },
"requests": { "requests": {
"hashes": [ "hashes": [
"sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
], ],
"index": "pypi", "index": "pypi",
"version": "==2.22.0" "version": "==2.23.0"
}, },
"urllib3": { "urllib3": {
"hashes": [ "hashes": [
"sha256:a8a318824cc77d1fd4b2bec2ded92646630d7fe8619497b142c84a9e6f5a7293", "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc",
"sha256:f3c5fd51747d450d4dcf6f923c81f78f811aab8205fda64b0aba34a4e48b0745" "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"
], ],
"version": "==1.25.7" "version": "==1.25.8"
} }
}, },
"develop": { "develop": {
@ -158,10 +158,10 @@
}, },
"decorator": { "decorator": {
"hashes": [ "hashes": [
"sha256:54c38050039232e1db4ad7375cfce6748d7b41c29e95a081c8a6d2c30364a2ce", "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
"sha256:5d19b92a3c8f7f101c8dd86afd86b0f061a8ce4540ab8cd401fa2542756bce6d" "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
], ],
"version": "==4.4.1" "version": "==4.4.2"
}, },
"entrypoints": { "entrypoints": {
"hashes": [ "hashes": [
@ -180,11 +180,11 @@
}, },
"ipython": { "ipython": {
"hashes": [ "hashes": [
"sha256:dfd303b270b7b5232b3d08bd30ec6fd685d8a58cabd54055e3d69d8f029f7280", "sha256:ca478e52ae1f88da0102360e57e528b92f3ae4316aabac80a2cd7f7ab2efb48a",
"sha256:ed7ebe1cba899c1c3ccad6f7f1c2d2369464cc77dba8eebc65e2043e19cda995" "sha256:eb8d075de37f678424527b5ef6ea23f7b80240ca031c2dd6de5879d687a65333"
], ],
"index": "pypi", "index": "pypi",
"version": "==7.9.0" "version": "==7.13.0"
}, },
"ipython-genutils": { "ipython-genutils": {
"hashes": [ "hashes": [
@ -203,10 +203,10 @@
}, },
"jedi": { "jedi": {
"hashes": [ "hashes": [
"sha256:786b6c3d80e2f06fd77162a07fed81b8baa22dde5d62896a790a331d6ac21a27", "sha256:b4f4052551025c6b0b0b193b29a6ff7bdb74c52450631206c262aef9f7159ad2",
"sha256:ba859c74fa3c966a22f2aeebe1b74ee27e2a462f56d3f5f7ca4a59af61bfe42e" "sha256:d5c871cb9360b414f981e7072c52c33258d598305280fef91c6cae34739d65d5"
], ],
"version": "==0.15.1" "version": "==0.16.0"
}, },
"mccabe": { "mccabe": {
"hashes": [ "hashes": [
@ -217,38 +217,39 @@
}, },
"more-itertools": { "more-itertools": {
"hashes": [ "hashes": [
"sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c",
"sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"
], ],
"version": "==7.2.0" "version": "==8.2.0"
}, },
"packaging": { "packaging": {
"hashes": [ "hashes": [
"sha256:28b924174df7a2fa32c1953825ff29c61e2f5e082343165438812f00d3a7fc47", "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73",
"sha256:d9551545c6d761f3def1677baf08ab2a3ca17c56879e70fecba2fc4dde4ed108" "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"
], ],
"version": "==19.2" "version": "==20.1"
}, },
"parso": { "parso": {
"hashes": [ "hashes": [
"sha256:63854233e1fadb5da97f2744b6b24346d2750b85965e7e399bec1620232797dc", "sha256:0c5659e0c6eba20636f99a04f469798dca8da279645ce5c387315b2c23912157",
"sha256:666b0ee4a7a1220f65d367617f2cd3ffddff3e205f3f16a0284df30e774c2a9c" "sha256:8515fc12cfca6ee3aa59138741fc5624d62340c97e401c74875769948d4f2995"
], ],
"version": "==0.5.1" "version": "==0.6.2"
}, },
"pathspec": { "pathspec": {
"hashes": [ "hashes": [
"sha256:e285ccc8b0785beadd4c18e5708b12bb8fcf529a1e61215b3feff1d1e559ea5c" "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
"sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
], ],
"version": "==0.6.0" "version": "==0.7.0"
}, },
"pexpect": { "pexpect": {
"hashes": [ "hashes": [
"sha256:2094eefdfcf37a1fdbfb9aa090862c1a4878e5c7e0e7e7088bdb511c558e5cd1", "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937",
"sha256:9e2c1fd0e6ee3a49b28f95d4b33bc389c89b20af6a1255906e90ff1262ce62eb" "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"
], ],
"markers": "sys_platform != 'win32'", "markers": "sys_platform != 'win32'",
"version": "==4.7.0" "version": "==4.8.0"
}, },
"pickleshare": { "pickleshare": {
"hashes": [ "hashes": [
@ -266,11 +267,10 @@
}, },
"prompt-toolkit": { "prompt-toolkit": {
"hashes": [ "hashes": [
"sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4", "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e",
"sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31", "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a"
"sha256:f15af68f66e664eaa559d4ac8a928111eebd5feda0c11738b5998045224829db"
], ],
"version": "==2.0.10" "version": "==3.0.3"
}, },
"ptyprocess": { "ptyprocess": {
"hashes": [ "hashes": [
@ -281,10 +281,10 @@
}, },
"py": { "py": {
"hashes": [ "hashes": [
"sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
"sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
], ],
"version": "==1.8.0" "version": "==1.8.1"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
@ -302,50 +302,71 @@
}, },
"pygments": { "pygments": {
"hashes": [ "hashes": [
"sha256:83ec6c6133ca6b529b7ff5aa826328fd14b5bb02a58c37f4f06384e96a0f94ab", "sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b",
"sha256:b7949de3d396836085fea596998b135a22610bbcc4f2abfe9e448e44cbc58388" "sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe"
], ],
"version": "==2.5.1" "version": "==2.5.2"
}, },
"pyparsing": { "pyparsing": {
"hashes": [ "hashes": [
"sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f", "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f",
"sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a" "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"
], ],
"version": "==2.4.5" "version": "==2.4.6"
}, },
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:63344a2e3bce2e4d522fd62b4fdebb647c019f1f9e4ca075debbd13219db4418", "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d",
"sha256:f67403f33b2b1d25a6756184077394167fe5e2f9d8bdaab30707d19ccec35427" "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.3.1" "version": "==5.3.5"
},
"pytest-clarity": {
"hashes": [
"sha256:5cc99e3d9b7969dfe17e5f6072d45a917c59d363b679686d3c958a1ded2e4dcf"
],
"index": "pypi",
"version": "==0.3.0a0"
}, },
"regex": { "regex": {
"hashes": [ "hashes": [
"sha256:15454b37c5a278f46f7aa2d9339bda450c300617ca2fca6558d05d870245edc7", "sha256:01b2d70cbaed11f72e57c1cfbaca71b02e3b98f739ce33f5f26f71859ad90431",
"sha256:1ad40708c255943a227e778b022c6497c129ad614bb7a2a2f916e12e8a359ee7", "sha256:046e83a8b160aff37e7034139a336b660b01dbfe58706f9d73f5cdc6b3460242",
"sha256:5e00f65cc507d13ab4dfa92c1232d004fa202c1d43a32a13940ab8a5afe2fb96", "sha256:113309e819634f499d0006f6200700c8209a2a8bf6bd1bdc863a4d9d6776a5d1",
"sha256:604dc563a02a74d70ae1f55208ddc9bfb6d9f470f6d1a5054c4bd5ae58744ab1", "sha256:200539b5124bc4721247a823a47d116a7a23e62cc6695744e3eb5454a8888e6d",
"sha256:720e34a539a76a1fedcebe4397290604cc2bdf6f81eca44adb9fb2ea071c0c69", "sha256:25f4ce26b68425b80a233ce7b6218743c71cf7297dbe02feab1d711a2bf90045",
"sha256:7caf47e4a9ac6ef08cabd3442cc4ca3386db141fb3c8b2a7e202d0470028e910", "sha256:269f0c5ff23639316b29f31df199f401e4cb87529eafff0c76828071635d417b",
"sha256:7faf534c1841c09d8fefa60ccde7b9903c9b528853ecf41628689793290ca143", "sha256:5de40649d4f88a15c9489ed37f88f053c15400257eeb18425ac7ed0a4e119400",
"sha256:b4e0406d822aa4993ac45072a584d57aa4931cf8288b5455bbf30c1d59dbad59", "sha256:7f78f963e62a61e294adb6ff5db901b629ef78cb2a1cfce3cf4eeba80c1c67aa",
"sha256:c31eaf28c6fe75ea329add0022efeed249e37861c19681960f99bbc7db981fb2", "sha256:82469a0c1330a4beb3d42568f82dffa32226ced006e0b063719468dcd40ffdf0",
"sha256:c7393597191fc2043c744db021643549061e12abe0b3ff5c429d806de7b93b66", "sha256:8c2b7fa4d72781577ac45ab658da44c7518e6d96e2a50d04ecb0fd8f28b21d69",
"sha256:d2b302f8cdd82c8f48e9de749d1d17f85ce9a0f082880b9a4859f66b07037dc6", "sha256:974535648f31c2b712a6b2595969f8ab370834080e00ab24e5dbb9d19b8bfb74",
"sha256:e3d8dd0ec0ea280cf89026b0898971f5750a7bd92cb62c51af5a52abd020054a", "sha256:99272d6b6a68c7ae4391908fc15f6b8c9a6c345a46b632d7fdb7ef6c883a2bbb",
"sha256:ec032cbfed59bd5a4b8eab943c310acfaaa81394e14f44454ad5c9eba4f24a74" "sha256:9b64a4cc825ec4df262050c17e18f60252cdd94742b4ba1286bcfe481f1c0f26",
"sha256:9e9624440d754733eddbcd4614378c18713d2d9d0dc647cf9c72f64e39671be5",
"sha256:9ff16d994309b26a1cdf666a6309c1ef51ad4f72f99d3392bcd7b7139577a1f2",
"sha256:b33ebcd0222c1d77e61dbcd04a9fd139359bded86803063d3d2d197b796c63ce",
"sha256:bba52d72e16a554d1894a0cc74041da50eea99a8483e591a9edf1025a66843ab",
"sha256:bed7986547ce54d230fd8721aba6fd19459cdc6d315497b98686d0416efaff4e",
"sha256:c7f58a0e0e13fb44623b65b01052dae8e820ed9b8b654bb6296bc9c41f571b70",
"sha256:d58a4fa7910102500722defbde6e2816b0372a4fcc85c7e239323767c74f5cbc",
"sha256:f1ac2dc65105a53c1c2d72b1d3e98c2464a133b4067a51a3d2477b28449709a0"
], ],
"version": "==2019.11.1" "version": "==2020.2.20"
}, },
"six": { "six": {
"hashes": [ "hashes": [
"sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
], ],
"version": "==1.13.0" "version": "==1.14.0"
},
"termcolor": {
"hashes": [
"sha256:1d6d69ce66211143803fbc56652b41d73b4a400a2891d7bf7a1cdf4c02de613b"
],
"version": "==1.1.0"
}, },
"toml": { "toml": {
"hashes": [ "hashes": [
@ -363,35 +384,36 @@
}, },
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355",
"sha256:18511a0b3e7922276346bcb47e2ef9f38fb90fd31cb9223eed42c85d1312344e", "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919",
"sha256:262c247a82d005e43b5b7f69aff746370538e176131c32dda9cb0f324d27141e", "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa",
"sha256:2b907eb046d049bcd9892e3076c7a6456c93a25bebfe554e931620c90e6a25b0", "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652",
"sha256:354c16e5babd09f5cb0ee000d54cfa38401d8b8891eefa878ac772f827181a3c", "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75",
"sha256:48e5b1e71f25cfdef98b013263a88d7145879fbb2d5185f2a0c79fa7ebbeae47", "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01",
"sha256:4e0b70c6fc4d010f8107726af5fd37921b666f5b31d9331f0bd24ad9a088e631", "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d",
"sha256:630968c5cdee51a11c05a30453f8cd65e0cc1d2ad0d9192819df9978984529f4", "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1",
"sha256:66480f95b8167c9c5c5c87f32cf437d585937970f3fc24386f313a4c97b44e34", "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907",
"sha256:71211d26ffd12d63a83e079ff258ac9d56a1376a25bc80b1cdcdf601b855b90b", "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c",
"sha256:7954560051331d003b4e2b3eb822d9dd2e376fa4f6d98fee32f452f52dd6ebb2", "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3",
"sha256:838997f4310012cf2e1ad3803bce2f3402e9ffb71ded61b5ee22617b3a7f6b6e", "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b",
"sha256:95bd11af7eafc16e829af2d3df510cecfd4387f6453355188342c3e79a2ec87a", "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614",
"sha256:bc6c7d3fa1325a0c6613512a093bc2a2a15aeec350451cbdf9e1d4bffe3e3233", "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb",
"sha256:cc34a6f5b426748a507dd5d1de4c1978f2eb5626d51326e43280941206c209e1", "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b",
"sha256:d755f03c1e4a51e9b24d899561fec4ccaf51f210d52abdf8c07ee2849b212a36", "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41",
"sha256:d7c45933b1bdfaf9f36c579671fec15d25b06c8398f113dab64c18ed1adda01d", "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6",
"sha256:d896919306dd0aa22d0132f62a1b78d11aaf4c9fc5b3410d3c666b818191630a", "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34",
"sha256:fdc1c9bbf79510b76408840e009ed65958feba92a88833cdceecff93ae8fff66", "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe",
"sha256:ffde2fbfad571af120fcbfbbc61c72469e72f550d676c3342492a9dfdefb8f12" "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
"sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
], ],
"version": "==1.4.0" "version": "==1.4.1"
}, },
"wcwidth": { "wcwidth": {
"hashes": [ "hashes": [
"sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603",
"sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"
], ],
"version": "==0.1.7" "version": "==0.1.8"
} }
} }
} }

View File

@ -27,11 +27,10 @@ class AllItemsResource:
cursor.execute("SELECT COUNT(id) FROM items") cursor.execute("SELECT COUNT(id) FROM items")
pages = round(cursor.fetchone()[0] / limit) pages = round(cursor.fetchone()[0] / limit)
# get statistics, ordered by id, and use limit and offset to page through results # get statistics and use limit and offset to page through results
cursor.execute( cursor.execute(
"SELECT id, views, downloads FROM items ORDER BY id ASC LIMIT {} OFFSET {}".format( "SELECT id, views, downloads FROM items LIMIT %s OFFSET %s",
limit, offset [limit, offset],
)
) )
# create a list to hold dicts of item stats # create a list to hold dicts of item stats
@ -41,7 +40,7 @@ class AllItemsResource:
for item in cursor: for item in cursor:
statistics.append( statistics.append(
{ {
"id": item["id"], "id": str(item["id"]),
"views": item["views"], "views": item["views"],
"downloads": item["downloads"], "downloads": item["downloads"],
} }
@ -61,26 +60,30 @@ class ItemResource:
def on_get(self, req, resp, item_id): def on_get(self, req, resp, item_id):
"""Handles GET requests""" """Handles GET requests"""
import psycopg2.extras
# Adapt Pythons uuid.UUID type to PostgreSQLs uuid
# See: https://www.psycopg.org/docs/extras.html
psycopg2.extras.register_uuid()
with DatabaseManager() as db: with DatabaseManager() as db:
db.set_session(readonly=True) db.set_session(readonly=True)
with db.cursor() as cursor: with db.cursor() as cursor:
cursor = db.cursor() cursor = db.cursor()
cursor.execute( cursor.execute(
"SELECT views, downloads FROM items WHERE id={}".format(item_id) "SELECT views, downloads FROM items WHERE id=%s", [str(item_id)]
) )
if cursor.rowcount == 0: if cursor.rowcount == 0:
raise falcon.HTTPNotFound( raise falcon.HTTPNotFound(
title="Item not found", title="Item not found",
description='The item with id "{}" was not found.'.format( description=f'The item with id "{str(item_id)}" was not found.',
item_id
),
) )
else: else:
results = cursor.fetchone() results = cursor.fetchone()
statistics = { statistics = {
"id": item_id, "id": str(item_id),
"views": results["views"], "views": results["views"],
"downloads": results["downloads"], "downloads": results["downloads"],
} }
@ -91,6 +94,6 @@ class ItemResource:
api = application = falcon.API() api = application = falcon.API()
api.add_route("/", RootResource()) api.add_route("/", RootResource())
api.add_route("/items", AllItemsResource()) api.add_route("/items", AllItemsResource())
api.add_route("/item/{item_id:int}", ItemResource()) api.add_route("/item/{item_id:uuid}", ItemResource())
# vim: set sw=4 ts=4 expandtab: # vim: set sw=4 ts=4 expandtab:

View File

@ -15,9 +15,7 @@ class DatabaseManager:
"""Manage database connection.""" """Manage database connection."""
def __init__(self): def __init__(self):
self._connection_uri = "dbname={} user={} password={} host={} port={}".format( self._connection_uri = f"dbname={DATABASE_NAME} user={DATABASE_USER} password={DATABASE_PASS} host={DATABASE_HOST} port={DATABASE_PORT}"
DATABASE_NAME, DATABASE_USER, DATABASE_PASS, DATABASE_HOST, DATABASE_PORT
)
def __enter__(self): def __enter__(self):
try: try:

View File

@ -70,13 +70,13 @@ def get_statistics_shards():
if len(statistics_core_years) > 0: if len(statistics_core_years) > 0:
# Begin building a string of shards starting with the default one # Begin building a string of shards starting with the default one
shards = "{}/statistics".format(SOLR_SERVER) shards = f"{SOLR_SERVER}/statistics"
for core in statistics_core_years: for core in statistics_core_years:
# Create a comma-separated list of shards to pass to our Solr query # Create a comma-separated list of shards to pass to our Solr query
# #
# See: https://wiki.apache.org/solr/DistributedSearch # See: https://wiki.apache.org/solr/DistributedSearch
shards += ",{}/{}".format(SOLR_SERVER, core) shards += f",{SOLR_SERVER}/{core}"
# Return the string of shards, which may actually be empty. Solr doesn't # Return the string of shards, which may actually be empty. Solr doesn't
# seem to mind if the shards query parameter is empty and I haven't seen # seem to mind if the shards query parameter is empty and I haven't seen
@ -134,9 +134,7 @@ def index_views():
while results_current_page <= results_num_pages: while results_current_page <= results_num_pages:
# "pages" are zero based, but one based is more human readable # "pages" are zero based, but one based is more human readable
print( print(
"Indexing item views (page {} of {})".format( f"Indexing item views (page {results_current_page + 1} of {results_num_pages + 1})"
results_current_page + 1, results_num_pages + 1
)
) )
solr_query_params = { solr_query_params = {
@ -219,9 +217,7 @@ def index_downloads():
while results_current_page <= results_num_pages: while results_current_page <= results_num_pages:
# "pages" are zero based, but one based is more human readable # "pages" are zero based, but one based is more human readable
print( print(
"Indexing item downloads (page {} of {})".format( f"Indexing item downloads (page {results_current_page + 1} of {results_num_pages + 1})"
results_current_page + 1, results_num_pages + 1
)
) )
solr_query_params = { solr_query_params = {
@ -264,7 +260,7 @@ with DatabaseManager() as db:
# create table to store item views and downloads # create table to store item views and downloads
cursor.execute( cursor.execute(
"""CREATE TABLE IF NOT EXISTS items """CREATE TABLE IF NOT EXISTS items
(id INT PRIMARY KEY, views INT DEFAULT 0, downloads INT DEFAULT 0)""" (id UUID PRIMARY KEY, views INT DEFAULT 0, downloads INT DEFAULT 0)"""
) )
# commit the table creation before closing the database connection # commit the table creation before closing the database connection

View File

@ -1,27 +1,37 @@
-i https://pypi.org/simple -i https://pypi.org/simple
appdirs==1.4.3
attrs==19.3.0 attrs==19.3.0
backcall==0.1.0 backcall==0.1.0
decorator==4.4.1 black==19.10b0
click==7.0
decorator==4.4.2
entrypoints==0.3 entrypoints==0.3
flake8==3.7.9 flake8==3.7.9
ipython-genutils==0.2.0 ipython-genutils==0.2.0
ipython==7.9.0 ipython==7.13.0
jedi==0.15.1 isort==4.3.21
jedi==0.16.0
mccabe==0.6.1 mccabe==0.6.1
more-itertools==7.2.0 more-itertools==8.2.0
packaging==19.2 packaging==20.1
parso==0.5.1 parso==0.6.2
pexpect==4.7.0 ; sys_platform != 'win32' pathspec==0.7.0
pexpect==4.8.0 ; sys_platform != 'win32'
pickleshare==0.7.5 pickleshare==0.7.5
pluggy==0.13.1 pluggy==0.13.1
prompt-toolkit==2.0.10 prompt-toolkit==3.0.3
ptyprocess==0.6.0 ptyprocess==0.6.0
py==1.8.0 py==1.8.1
pycodestyle==2.5.0 pycodestyle==2.5.0
pyflakes==2.1.1 pyflakes==2.1.1
pygments==2.5.1 pygments==2.5.2
pyparsing==2.4.5 pyparsing==2.4.6
pytest==5.3.1 pytest-clarity==0.3.0a0
six==1.13.0 pytest==5.3.5
regex==2020.2.20
six==1.14.0
termcolor==1.1.0
toml==0.10.0
traitlets==4.3.3 traitlets==4.3.3
wcwidth==0.1.7 typed-ast==1.4.1
wcwidth==0.1.8

View File

@ -1,9 +1,9 @@
-i https://pypi.org/simple -i https://pypi.org/simple
certifi==2019.9.11 certifi==2019.11.28
chardet==3.0.4 chardet==3.0.4
falcon==2.0.0 falcon==2.0.0
gunicorn==20.0.4 gunicorn==20.0.4
idna==2.8 idna==2.9
psycopg2-binary==2.8.4 psycopg2-binary==2.8.4
requests==2.22.0 requests==2.23.0
urllib3==1.25.7 urllib3==1.25.8

View File

@ -11,57 +11,57 @@ def client():
def test_get_docs(client): def test_get_docs(client):
'''Test requesting the documentation at the root.''' """Test requesting the documentation at the root."""
response = client.simulate_get('/') response = client.simulate_get("/")
assert isinstance(response.content, bytes) assert isinstance(response.content, bytes)
assert response.status_code == 200 assert response.status_code == 200
def test_get_item(client): def test_get_item(client):
'''Test requesting a single item.''' """Test requesting a single item."""
response = client.simulate_get('/item/17') response = client.simulate_get("/item/c3910974-c3a5-4053-9dce-104aa7bb1621")
response_doc = json.loads(response.text) response_doc = json.loads(response.text)
assert isinstance(response_doc['downloads'], int) assert isinstance(response_doc["downloads"], int)
assert isinstance(response_doc['id'], int) assert isinstance(response_doc["id"], str)
assert isinstance(response_doc['views'], int) assert isinstance(response_doc["views"], int)
assert response.status_code == 200 assert response.status_code == 200
def test_get_missing_item(client): def test_get_missing_item(client):
'''Test requesting a single non-existing item.''' """Test requesting a single non-existing item."""
response = client.simulate_get('/item/1') response = client.simulate_get("/item/c3910974-c3a5-4053-9dce-104aa7bb1620")
assert response.status_code == 404 assert response.status_code == 404
def test_get_items(client): def test_get_items(client):
'''Test requesting 100 items.''' """Test requesting 100 items."""
response = client.simulate_get('/items', query_string='limit=100') response = client.simulate_get("/items", query_string="limit=100")
response_doc = json.loads(response.text) response_doc = json.loads(response.text)
assert isinstance(response_doc['currentPage'], int) assert isinstance(response_doc["currentPage"], int)
assert isinstance(response_doc['totalPages'], int) assert isinstance(response_doc["totalPages"], int)
assert isinstance(response_doc['statistics'], list) assert isinstance(response_doc["statistics"], list)
assert response.status_code == 200 assert response.status_code == 200
def test_get_items_invalid_limit(client): def test_get_items_invalid_limit(client):
'''Test requesting 100 items with an invalid limit parameter.''' """Test requesting 100 items with an invalid limit parameter."""
response = client.simulate_get('/items', query_string='limit=101') response = client.simulate_get("/items", query_string="limit=101")
assert response.status_code == 400 assert response.status_code == 400
def test_get_items_invalid_page(client): def test_get_items_invalid_page(client):
'''Test requesting 100 items with an invalid page parameter.''' """Test requesting 100 items with an invalid page parameter."""
response = client.simulate_get('/items', query_string='page=-1') response = client.simulate_get("/items", query_string="page=-1")
assert response.status_code == 400 assert response.status_code == 400