mirror of
https://github.com/ilri/dspace-statistics-api.git
synced 2025-05-23 20:42:25 +02:00
Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
13736d6359
|
|||
4fc64edeb8 | |||
2a8901dc4f
|
|||
e25c974796
|
|||
ffc62e9ee6
|
|||
556c5ae088
|
|||
d94134f80a
|
|||
586231eb2d
|
|||
766b77a3b6
|
|||
1959e8154e
|
|||
d40b2f0b2e | |||
061d0a8f5f | |||
e57660ff88 | |||
5c8756bede | |||
bae9fb80e4 | |||
8a65d99e08
|
|||
d479b7dc6c
|
|||
40aac8bf89 | |||
53ba6f2936
|
|||
140cc4cb07
|
|||
d5d2d2149b
|
16
.travis.yml
16
.travis.yml
@ -3,9 +3,17 @@ python:
|
|||||||
- "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
- "3.7-dev"
|
- "3.7-dev"
|
||||||
script: pip install -r requirements.txt
|
addons:
|
||||||
branches:
|
postgresql: "9.5"
|
||||||
only:
|
before_script:
|
||||||
- master
|
- psql --version
|
||||||
|
- createuser -U postgres dspacestatistics
|
||||||
|
- psql -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'"
|
||||||
|
- createdb -U postgres -O dspacestatistics --encoding=UNICODE dspacestatistics
|
||||||
|
- psql -U postgres -d dspacestatistics < tests/dspacestatistics.sql
|
||||||
|
install:
|
||||||
|
- "pip install pipenv --upgrade-strategy=only-if-needed"
|
||||||
|
- "pipenv install --dev"
|
||||||
|
script: pytest
|
||||||
|
|
||||||
# vim: ts=2 sw=2 et
|
# vim: ts=2 sw=2 et
|
||||||
|
45
CHANGELOG.md
45
CHANGELOG.md
@ -4,46 +4,53 @@ 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).
|
||||||
|
|
||||||
### [0.7.0] - 2018-11-07
|
## [0.8.0] - 2018-11-11
|
||||||
## Added
|
### Changed
|
||||||
|
- Properly handle database connection errors
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- API tests with pytest
|
||||||
|
|
||||||
|
## [0.7.0] - 2018-11-07
|
||||||
|
### Added
|
||||||
- Ability to configure PostgreSQL database port with DATABASE_PORT environment variable (defaults to 5432)
|
- Ability to configure PostgreSQL database port with DATABASE_PORT environment variable (defaults to 5432)
|
||||||
- Hound CI configuration to validate pull requests against PEP 8 code style with Flake8
|
- Hound CI configuration to validate pull requests against PEP 8 code style with Flake8
|
||||||
- Configuration for [pipenv](https://pipenv.readthedocs.io/en/latest/)
|
- Configuration for [pipenv](https://pipenv.readthedocs.io/en/latest/)
|
||||||
|
|
||||||
## Changed
|
### Changed
|
||||||
- Use a database management class with Python context management to automatically open/close connections and cursors
|
- Use a database management class with Python context management to automatically open/close connections and cursors
|
||||||
|
|
||||||
## Changed
|
### Changed
|
||||||
- Validate code against PEP 8 style guide with Flake8
|
- Validate code against PEP 8 style guide with Flake8
|
||||||
|
|
||||||
### [0.6.1] - 2018-10-31
|
## [0.6.1] - 2018-10-31
|
||||||
## Added
|
### Added
|
||||||
- API documentation at root path (/)
|
- API documentation at root path (/)
|
||||||
|
|
||||||
### [0.6.0] - 2018-10-31
|
## [0.6.0] - 2018-10-31
|
||||||
## Changed
|
### Changed
|
||||||
- Refactor project structure (note breaking changes to API and indexing invocation, see contrib and README.md)
|
- Refactor project structure (note breaking changes to API and indexing invocation, see contrib and README.md)
|
||||||
|
|
||||||
### [0.5.2] - 2018-10-28
|
## [0.5.2] - 2018-10-28
|
||||||
## Changed
|
### Changed
|
||||||
- Update library versions in requirements.txt
|
- Update library versions in requirements.txt
|
||||||
|
|
||||||
### [0.5.1] - 2018-10-24
|
## [0.5.1] - 2018-10-24
|
||||||
## Changed
|
### Changed
|
||||||
- Use Python's native json instead of ujson
|
- Use Python's native json instead of ujson
|
||||||
|
|
||||||
### [0.5.0] - 2018-10-24
|
## [0.5.0] - 2018-10-24
|
||||||
## Added
|
### Added
|
||||||
- Example nginx configuration to README.md
|
- Example nginx configuration to README.md
|
||||||
|
|
||||||
## Changed
|
### Changed
|
||||||
- Don't initialize Solr connection in API
|
- Don't initialize Solr connection in API
|
||||||
|
|
||||||
### [0.4.3] - 2018-10-17
|
## [0.4.3] - 2018-10-17
|
||||||
## Changed
|
### Changed
|
||||||
- Use pip install as script for Travis CI
|
- Use pip install as script for Travis CI
|
||||||
|
|
||||||
## Improved
|
### Improved
|
||||||
- Documentation for deployment and testing
|
- Documentation for deployment and testing
|
||||||
|
|
||||||
## [0.4.2] - 2018-10-04
|
## [0.4.2] - 2018-10-04
|
||||||
@ -57,7 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [0.4.1] - 2018-09-26
|
## [0.4.1] - 2018-09-26
|
||||||
### Changed
|
### Changed
|
||||||
- Use execute_values() to batch insert records to PostgreSQL
|
- Use `execute_values()` to batch insert records to PostgreSQL
|
||||||
|
|
||||||
## [0.4.0] - 2018-09-25
|
## [0.4.0] - 2018-09-25
|
||||||
### Fixed
|
### Fixed
|
||||||
|
1
Pipfile
1
Pipfile
@ -20,6 +20,7 @@ solrclient = {ref = "kazoo-2.5.0", git = "https://github.com/alanorth/SolrClient
|
|||||||
[dev-packages]
|
[dev-packages]
|
||||||
"flake8" = "*"
|
"flake8" = "*"
|
||||||
ipython = "*"
|
ipython = "*"
|
||||||
|
pytest = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.6"
|
python_version = "3.6"
|
||||||
|
46
Pipfile.lock
generated
46
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "74430260b3271348f65792cc7f9cadc5d2036abc4a5fc958524239656ffabb4f"
|
"sha256": "da754a9f6c668303b071155fdd3a31067897f2c1703a28990745506a5ea55538"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -138,6 +138,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"develop": {
|
"develop": {
|
||||||
|
"atomicwrites": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:0312ad34fcad8fac3704d441f7b317e50af620823353ec657a53e981f92920c0",
|
||||||
|
"sha256:ec9ae8adaae229e4f8446952d204a3e4b5fdd2d099f9be3aaf556120135fb3ee"
|
||||||
|
],
|
||||||
|
"version": "==1.2.1"
|
||||||
|
},
|
||||||
|
"attrs": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
|
||||||
|
"sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
|
||||||
|
],
|
||||||
|
"version": "==18.2.0"
|
||||||
|
},
|
||||||
"backcall": {
|
"backcall": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
|
"sha256:38ecd85be2c1e78f77fd91700c76e14667dc21e2713b63876c0eb901196e01e4",
|
||||||
@ -189,6 +203,14 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.6.1"
|
"version": "==0.6.1"
|
||||||
},
|
},
|
||||||
|
"more-itertools": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092",
|
||||||
|
"sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e",
|
||||||
|
"sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d"
|
||||||
|
],
|
||||||
|
"version": "==4.3.0"
|
||||||
|
},
|
||||||
"parso": {
|
"parso": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
|
"sha256:35704a43a3c113cce4de228ddb39aab374b8004f4f2407d070b6a2ca784ce8a2",
|
||||||
@ -211,6 +233,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.7.5"
|
"version": "==0.7.5"
|
||||||
},
|
},
|
||||||
|
"pluggy": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:447ba94990e8014ee25ec853339faf7b0fc8050cdc3289d4d71f7f410fb90095",
|
||||||
|
"sha256:bde19360a8ec4dfd8a20dcb811780a30998101f078fc7ded6162f0076f50508f"
|
||||||
|
],
|
||||||
|
"version": "==0.8.0"
|
||||||
|
},
|
||||||
"prompt-toolkit": {
|
"prompt-toolkit": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34",
|
"sha256:c1d6aff5252ab2ef391c2fe498ed8c088066f66bc64a8d5c095bbf795d9fec34",
|
||||||
@ -226,6 +255,13 @@
|
|||||||
],
|
],
|
||||||
"version": "==0.6.0"
|
"version": "==0.6.0"
|
||||||
},
|
},
|
||||||
|
"py": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:bf92637198836372b520efcba9e020c330123be8ce527e535d185ed4b6f45694",
|
||||||
|
"sha256:e76826342cefe3c3d5f7e8ee4316b80d1dd8a300781612ddbc765c17ba25a6c6"
|
||||||
|
],
|
||||||
|
"version": "==1.7.0"
|
||||||
|
},
|
||||||
"pycodestyle": {
|
"pycodestyle": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
|
"sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
|
||||||
@ -247,6 +283,14 @@
|
|||||||
],
|
],
|
||||||
"version": "==2.2.0"
|
"version": "==2.2.0"
|
||||||
},
|
},
|
||||||
|
"pytest": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:630ff1dbe04f469ee78faa5660f712e58b953da7df22ea5d828c9012e134da43",
|
||||||
|
"sha256:a2b5232735dd0b736cbea9c0f09e5070d78fcaba2823a4f6f09d9a81bd19415c"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==3.10.0"
|
||||||
|
},
|
||||||
"six": {
|
"six": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
|
||||||
|
@ -35,6 +35,10 @@ Test to see if there are any statistics:
|
|||||||
|
|
||||||
$ curl 'http://localhost:8000/items?limit=1'
|
$ curl 'http://localhost:8000/items?limit=1'
|
||||||
|
|
||||||
|
Run tests:
|
||||||
|
|
||||||
|
$ pytest
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
There are example systemd service and timer units in the `contrib` directory. The API service listens on localhost by default so you will need to expose it publicly using a web server like nginx.
|
There are example systemd service and timer units in the `contrib` directory. The API service listens on localhost by default so you will need to expose it publicly using a web server like nginx.
|
||||||
|
|
||||||
@ -71,8 +75,6 @@ The item id is the *internal* id for an item. You can get these from the standar
|
|||||||
## Todo
|
## Todo
|
||||||
|
|
||||||
- Better logging
|
- Better logging
|
||||||
- Tests
|
|
||||||
- Check if database exists (try/except)
|
|
||||||
- Version API
|
- Version API
|
||||||
- Use JSON in PostgreSQL
|
- Use JSON in PostgreSQL
|
||||||
- Switch to [Python 3.6+ f-string syntax](https://realpython.com/python-f-strings/)
|
- Switch to [Python 3.6+ f-string syntax](https://realpython.com/python-f-strings/)
|
||||||
|
@ -3,6 +3,7 @@ from .config import DATABASE_USER
|
|||||||
from .config import DATABASE_PASS
|
from .config import DATABASE_PASS
|
||||||
from .config import DATABASE_HOST
|
from .config import DATABASE_HOST
|
||||||
from .config import DATABASE_PORT
|
from .config import DATABASE_PORT
|
||||||
|
import falcon
|
||||||
import psycopg2
|
import psycopg2
|
||||||
import psycopg2.extras
|
import psycopg2.extras
|
||||||
|
|
||||||
@ -14,7 +15,13 @@ class DatabaseManager():
|
|||||||
self._connection_uri = 'dbname={} user={} password={} host={} port={}'.format(DATABASE_NAME, DATABASE_USER, DATABASE_PASS, DATABASE_HOST, DATABASE_PORT)
|
self._connection_uri = 'dbname={} user={} password={} host={} port={}'.format(DATABASE_NAME, DATABASE_USER, DATABASE_PASS, DATABASE_HOST, DATABASE_PORT)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
try:
|
||||||
self._connection = psycopg2.connect(self._connection_uri, cursor_factory=psycopg2.extras.DictCursor)
|
self._connection = psycopg2.connect(self._connection_uri, cursor_factory=psycopg2.extras.DictCursor)
|
||||||
|
except psycopg2.OperationalError:
|
||||||
|
title = '500 Internal Server Error'
|
||||||
|
description = 'Could not connect to database'
|
||||||
|
raise falcon.HTTPInternalServerError(title, description)
|
||||||
|
|
||||||
return self._connection
|
return self._connection
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||||
|
4
pytest.ini
Normal file
4
pytest.ini
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[pytest]
|
||||||
|
addopts= -rsxX -s -v --strict
|
||||||
|
filterwarnings =
|
||||||
|
error::UserWarning
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
77226
tests/dspacestatistics.sql
Normal file
77226
tests/dspacestatistics.sql
Normal file
File diff suppressed because it is too large
Load Diff
67
tests/test_api.py
Normal file
67
tests/test_api.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
from falcon import testing
|
||||||
|
import json
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from dspace_statistics_api.app import api
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client():
|
||||||
|
return testing.TestClient(api)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_docs(client):
|
||||||
|
'''Test requesting the documentation at the root.'''
|
||||||
|
|
||||||
|
response = client.simulate_get('/')
|
||||||
|
|
||||||
|
assert isinstance(response.content, bytes)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_item(client):
|
||||||
|
'''Test requesting a single item.'''
|
||||||
|
|
||||||
|
response = client.simulate_get('/item/17')
|
||||||
|
response_doc = json.loads(response.text)
|
||||||
|
|
||||||
|
assert isinstance(response_doc['downloads'], int)
|
||||||
|
assert isinstance(response_doc['id'], int)
|
||||||
|
assert isinstance(response_doc['views'], int)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_missing_item(client):
|
||||||
|
'''Test requesting a single non-existing item.'''
|
||||||
|
|
||||||
|
response = client.simulate_get('/item/1')
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_items(client):
|
||||||
|
'''Test requesting 100 items.'''
|
||||||
|
|
||||||
|
response = client.simulate_get('/items', query_string='limit=100')
|
||||||
|
response_doc = json.loads(response.text)
|
||||||
|
|
||||||
|
assert isinstance(response_doc['currentPage'], int)
|
||||||
|
assert isinstance(response_doc['totalPages'], int)
|
||||||
|
assert isinstance(response_doc['statistics'], list)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_items_invalid_limit(client):
|
||||||
|
'''Test requesting 100 items with an invalid limit parameter.'''
|
||||||
|
|
||||||
|
response = client.simulate_get('/items', query_string='limit=101')
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_items_invalid_page(client):
|
||||||
|
'''Test requesting 100 items with an invalid page parameter.'''
|
||||||
|
|
||||||
|
response = client.simulate_get('/items', query_string='page=-1')
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
Reference in New Issue
Block a user