mirror of
				https://github.com/ilri/dspace-statistics-api.git
				synced 2025-10-31 04:41:19 +01:00 
			
		
		
		
	Compare commits
	
		
			372 Commits
		
	
	
		
			v0.0.1
			...
			f3a0e3a671
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f3a0e3a671 | |||
| 4590fc8708 | |||
| 8b924cf450 | |||
| ea24c73a6a | |||
| cd98d33615 | |||
| 9d112266ca | |||
| 2b067050ff | |||
| dc683f2d1c | |||
| f60f529bd7 | |||
| 7db8458201 | |||
| 707f878b94 | |||
| 930250352a | |||
| e27f30ba4d | |||
| 28d1917038 | |||
| fc6a9c2ad1 | |||
| 3125e96a16 | |||
| 66143ff00f | |||
| 2d15f12be9 | |||
| 9218039e61 | |||
| 88a8db6c78 | |||
| 3995eba0a7 | |||
| 810508d038 | |||
| ecafab57cb | |||
| 9c9431b58c | |||
| 2d6520fc97 | |||
| 79a393d33f | |||
| 149f6c418f | |||
| ca1582a8b6 | |||
| 1904c243a4 | |||
| 0baa07f70a | |||
| 59214ffcb6 | |||
| 549b8bf1a7 | |||
| 899a79b2e7 | |||
| 4c59469055 | |||
| 4e9064329d | |||
| 4958d5d2e9 | |||
| 923ed0a434 | |||
| 5acd927210 | |||
| 630fa0d5fb | |||
| 58d2b8d4ed | |||
| e6572d9469 | |||
| 85fca81611 | |||
| c81a8d03d7 | |||
| 2923a3b325 | |||
| d4518d62ad | |||
| 3a98de78e3 | |||
| b26439daf3 | |||
| 9e898ba54f | |||
| 716d65030c | |||
| 5a53b57b3b | |||
| 3ceb9a6eb0 | |||
| 946f0749e2 | |||
| b06651d1ec | |||
| a0ee181361 | |||
| f58c209609 | |||
| 6dbff1e78f | |||
| 731226ec15 | |||
| 2201d3df4e | |||
| 2c0436f845 | |||
| f1e939481b | |||
| 4d8026a3d0 | |||
| de1f462ad2 | |||
| 8a6bbfd527 | |||
| 73c71fa8a0 | |||
| 7a5e14716d | |||
| 21b500b4f7 | |||
| 495386856b | |||
| 8e87f80e9a | |||
| c4bf8bf698 | |||
| 6ff95bb5f2 | |||
| 0c8fb21f80 | |||
| b359c2466f | |||
| 0eaed3e8c4 | |||
| 70e96214c8 | |||
| cab9f16dbc | |||
| bd49e1d1f6 | |||
| 144ed9a7c4 | |||
| 48eef8c8e3 | |||
| fa9325e8a3 | |||
| 998e833470 | |||
| dd8252601f | |||
| 9a9555853f | |||
| 385e92cc5e | |||
| b0e6481961 | |||
| f96a903be3 | |||
| fcf8fa4c29 | |||
| 5dd50ff998 | |||
| 6704e7375f | |||
| 37630d8dac | |||
| 0ef071a91d | |||
| 9e7dd28156 | |||
| 60e6ea57b1 | |||
| 5955868b9a | |||
| 250fd8164f | |||
| 82be1a4d00 | |||
| 0615064e3d | |||
| 76be1b749a | |||
| 92146fe426 | |||
| 440b2f2dfa | |||
| 67bc30ead0 | |||
| 142959acdb | |||
| 322f5a8db8 | |||
| 90dcaa6ec6 | |||
| 9aca827d69 | |||
| 1b394ec50e | |||
| 3e9753b600 | |||
| cb3c3d37fa | |||
| 4ff1fd4a22 | |||
| d2fe420a9a | |||
| 3197b79578 | |||
| eeb8e6bba1 | |||
| 3540ce328b | |||
| 520e04f9be | |||
| 8a46a64cfc | |||
| b8442f8cce | |||
| 95f7871cc1 | |||
| 3bc07027e5 | |||
| afcc445855 | |||
| 494548c691 | |||
| feb60b6adf | |||
| 1541ae3e3b | |||
| 1aedc0ca29 | |||
| a648183f35 | |||
| b8f379e7fa | |||
| 78f9949ecb | |||
| af80c4b447 | |||
| edd9e90f59 | |||
| 1806d50a51 | |||
| a459e66fd9 | |||
| 5a3b392a1d | |||
| 9dcda114c6 | |||
| 2b8aba5835 | |||
| 9eb30a98e3 | |||
| 622e9a86f1 | |||
| 2acd08e0ab | |||
| f75bcf292c | |||
| 8f46ceb8d8 | |||
| 18e1e1a227 | |||
| fd46041698 | |||
| 4ce7231ece | |||
| 60689d9014 | |||
| 7bca32189a | |||
| 94c5d91d3c | |||
| a640f734c8 | |||
| d56a3420f7 | |||
| 7add0d6164 | |||
| c86bec4d8f | |||
| 5429fe5cc8 | |||
| f8a4cfd3da | |||
| be94c94433 | |||
| ba49b78a25 | |||
| 842f80036f | |||
| f738b8029b | |||
| d08c43f3d5 | |||
| 819f8e6b0d | |||
| c79e50a364 | |||
| 71006d8bbf | |||
| b7d723ef7c | |||
| 914ec52fbb | |||
| 5524066656 | |||
| 043d897cef | |||
| bd28353cda | |||
| e23d66c2a2 | |||
| 40e284dac0 | |||
| 934fa9db9b | |||
| 1fabb72b58 | |||
| c7f95f0b60 | |||
| c95a98dd2d | |||
| 3f70f94a10 | |||
| 9b8ad9defd | |||
| d69ab20220 | |||
| 378f56ddc2 | |||
| 5a2a7d684c | |||
| 18276e910f | |||
| 8de8c2765f | |||
| 11a1755e59 | |||
| a835b0fdc5 | |||
| a88600c92b | |||
| 019d9242c9 | |||
| f4d7312a3f | |||
| 9c46cfc7e2 | |||
| c1c2e319ac | |||
| 0895b4f469 | |||
| dcfef06a65 | |||
| 13736d6359 | |||
| 4fc64edeb8 | |||
| 2a8901dc4f | |||
| e25c974796 | |||
| ffc62e9ee6 | |||
| 556c5ae088 | |||
| d94134f80a | |||
| 586231eb2d | |||
| 766b77a3b6 | |||
| 1959e8154e | |||
| d40b2f0b2e | |||
| 061d0a8f5f | |||
| e57660ff88 | |||
| 5c8756bede | |||
| bae9fb80e4 | |||
| 8a65d99e08 | |||
| d479b7dc6c | |||
| 40aac8bf89 | |||
| 53ba6f2936 | |||
| 140cc4cb07 | |||
| d5d2d2149b | |||
| 4c51d12eb4 | |||
| a6ce44e852 | |||
| f6e866a589 | |||
| eb5c187d41 | |||
| b06c82bb16 | |||
| 2f342be948 | |||
| e39f2b260c | |||
| 60ad474b88 | |||
| 888f85d19e | |||
| df7de93964 | |||
| 7218631cc4 | |||
| 085e525b2f | |||
| e1580df12f | |||
| be18779ff9 | |||
| 60cfd8f23b | |||
| 87fd117d77 | |||
| f262ebdca2 | |||
| 64d7f1a3b2 | |||
| a238a727d2 | |||
| cc5ce3ab98 | |||
| 70dfcb93c5 | |||
| 69bcd1b5e4 | |||
| 5f3bd61998 | |||
| e54dd8888f | |||
| 2ba09f8693 | |||
| a468a87a5a | |||
| 6a30b6550d | |||
| 18f013bfa0 | |||
| 78900b5d85 | |||
| eb08832bf8 | |||
| c2ec780ad9 | |||
| df8ebc8bf1 | |||
| 0d4be5f4c8 | |||
| 30dc7f1939 | |||
| 77194707fd | |||
| 10c1f8bdcc | |||
| da74943da2 | |||
| fc8348ab29 | |||
| 15c3299b99 | |||
| d36be5ee50 | |||
| 2f45d27554 | |||
| b8356f7a87 | |||
| 2136dc79ce | |||
| ed60120cef | |||
| c027f01b48 | |||
| 754663f062 | |||
| 507699e58a | |||
| a016916995 | |||
| 6fd2827a7c | |||
| 62142eb79e | |||
| fda0321942 | |||
| 963aa245c8 | |||
| 568ff2eebb | |||
| deecb8a10b | |||
| 12f45d7c08 | |||
| f65089f9ce | |||
| 1db5cf1c29 | |||
| e581c4b1aa | |||
| e8d356c9ca | |||
| 34a9b8d629 | |||
| 41e3d66a0e | |||
| 9b2a6137b4 | |||
| 600b986f99 | |||
| 49a7790794 | |||
| f2deba627c | |||
| 9323513794 | |||
| daf15610f2 | |||
| 4ede966dbb | |||
| 3580473a6d | |||
| 071c24535f | |||
| 4291aecac4 | |||
| 46bf537e88 | |||
| eaca5354d3 | |||
| 4600288ee4 | |||
| 8179563378 | |||
| b14c3eef4d | |||
| 71a789b13f | |||
| c68ddacaa4 | |||
| 9c9e79769e | |||
| 2ad5ade556 | |||
| 7412a09670 | |||
| bb744a00b8 | |||
| 7499b89d99 | |||
| 2c1e4952b1 | |||
| 379f202c3f | |||
| 560fa6056d | |||
| 385a34e5d0 | |||
| d0ea62d2bd | |||
| 366ae25b8e | |||
| 0f3054ae03 | |||
| 6bf34235d4 | |||
| e604d8ca81 | |||
| fc35b816f3 | |||
| 9e6a2f7559 | |||
| 46cfc3ffbc | |||
| 2850035a4c | |||
| c0b550109a | |||
| bfceffd84d | |||
| d0552f5047 | |||
| c3a0bf7f44 | |||
| 6e47e9c9ee | |||
| cd90d618d6 | |||
| 280d211d56 | |||
| 806d63137f | |||
| f7c7390e4f | |||
| 702724e8a4 | |||
| 36818d03ef | |||
| 4cf8656b35 | |||
| f30a464cd1 | |||
| 93ae12e313 | |||
| dc978e9333 | |||
| 295436fea0 | |||
| 46a1476ab0 | |||
| 87dbb6c4df | |||
| 3160c44566 | |||
| 4b72f626d9 | |||
| 2d3b7620e3 | |||
| 6e4bc630f7 | |||
| 44884140e5 | |||
| 74ff86ee3b | |||
| 3327884f21 | |||
| 8f7450f67a | |||
| 28d61fb041 | |||
| cbc98991b4 | |||
| 6c28be0463 | |||
| 42e8f17305 | |||
| 19a45f3f6f | |||
| 505ef31101 | |||
| 1543cacc54 | |||
| 2cab456f16 | |||
| 53615dea2d | |||
| 2d8d1e6833 | |||
| e26e595ea1 | |||
| a9151b5bbf | |||
| 76833d6f5f | |||
| a51422273c | |||
| 89621af85d | |||
| c554404d7f | |||
| 90d7a452bd | |||
| 431a1c9d64 | |||
| e1b9d1284f | |||
| bac764a0a4 | |||
| 1a650e57c0 | |||
| 2db5e02be9 | |||
| 9e942736b1 | |||
| ea85393b13 | |||
| cbeb7c89a7 | |||
| b0d81a543c | |||
| 84801a4ab5 | |||
| 4e8621e3d9 | |||
| 2c8430171d | |||
| fb60133713 | |||
| 9e01a80011 | |||
| a263996582 | |||
| ed9d25294e | |||
| 5e165d2e88 | |||
| 8e29fd8a43 | |||
| 24af83b03f | |||
| a87aaba812 | |||
| 57faec59c8 | |||
| 06ab254017 | |||
| 5b5cab8b34 | |||
| 40ce3c72a9 | |||
| ea2283355b | |||
| 4b4a959a1c | |||
| 1e16beed30 | |||
| 182e13efca | 
							
								
								
									
										21
									
								
								.build.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								.build.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| image: archlinux | ||||
| packages: | ||||
|   - python-poetry | ||||
|   - postgresql | ||||
| sources: | ||||
|   - https://git.sr.ht/~alanorth/dspace-statistics-api | ||||
| tasks: | ||||
|   - setup: | | ||||
|       id | ||||
|       psql --version | ||||
|       sudo su - postgres -c "initdb --locale en_US.UTF-8 -E UTF8 -D '/var/lib/postgres/data'" | ||||
|       sudo systemctl start postgresql | ||||
|       createuser -U postgres dspacestatistics | ||||
|       psql -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'" | ||||
|       createdb -U postgres -O dspacestatistics --encoding=UNICODE dspacestatistics | ||||
|       cd dspace-statistics-api | ||||
|       psql -U postgres -d dspacestatistics < tests/dspacestatistics.sql | ||||
|       poetry install --no-root | ||||
|   - test: | | ||||
|       cd dspace-statistics-api | ||||
|       poetry run pytest | ||||
							
								
								
									
										153
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								.drone.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: python39 | ||||
|  | ||||
| steps: | ||||
| - name: setup | ||||
|   image: postgres:10-alpine | ||||
|   environment: | ||||
|     PGPASSWORD: postgres | ||||
|   commands: | ||||
|   - id | ||||
|   - psql --version | ||||
|   - sleep 5 | ||||
|   - pg_isready -h database -U postgres -d dspacestatistics | ||||
|   - createuser -h database -U postgres dspacestatistics | ||||
|   - psql -h database -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'" | ||||
|   - psql -h database -U postgres -d dspacestatistics < tests/dspacestatistics.sql | ||||
|  | ||||
| - name: test | ||||
|   image: python:3.9-slim | ||||
|   environment: | ||||
|     PGPASSWORD: dspacestatistics | ||||
|     DATABASE_HOST: database | ||||
|   commands: | ||||
|   - id | ||||
|   - python -V | ||||
|   - apt update && apt install -y gcc | ||||
|   - pip install -r requirements-dev.txt | ||||
|   - pytest | ||||
|  | ||||
| services: | ||||
| - name: database | ||||
|   image: postgres:10-alpine | ||||
|   environment: | ||||
|     POSTGRES_USER: postgres | ||||
|     POSTGRES_PASSWORD: postgres | ||||
|     POSTGRES_DB: dspacestatistics | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: python38 | ||||
|  | ||||
| steps: | ||||
| - name: database | ||||
|   image: postgres:10-alpine | ||||
|   detach: true | ||||
|   environment: | ||||
|     POSTGRES_USER: postgres | ||||
|     POSTGRES_PASSWORD: postgres | ||||
|     POSTGRES_DB: dspacestatistics | ||||
|  | ||||
| - name: setup | ||||
|   image: postgres:10-alpine | ||||
|   environment: | ||||
|     PGPASSWORD: postgres | ||||
|   commands: | ||||
|   - id | ||||
|   - psql --version | ||||
|   - sleep 5 | ||||
|   - pg_isready -h database -U postgres -d dspacestatistics | ||||
|   - createuser -h database -U postgres dspacestatistics | ||||
|   - psql -h database -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'" | ||||
|   - psql -h database -U postgres -d dspacestatistics < tests/dspacestatistics.sql | ||||
|  | ||||
| - name: test | ||||
|   image: python:3.8-slim | ||||
|   environment: | ||||
|     PGPASSWORD: dspacestatistics | ||||
|     DATABASE_HOST: database | ||||
|   commands: | ||||
|   - id | ||||
|   - python -V | ||||
|   - pip install -r requirements-dev.txt | ||||
|   - pytest | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: python37 | ||||
|  | ||||
| steps: | ||||
| - name: database | ||||
|   image: postgres:10-alpine | ||||
|   detach: true | ||||
|   environment: | ||||
|     POSTGRES_USER: postgres | ||||
|     POSTGRES_PASSWORD: postgres | ||||
|     POSTGRES_DB: dspacestatistics | ||||
|  | ||||
| - name: setup | ||||
|   image: postgres:10-alpine | ||||
|   environment: | ||||
|     PGPASSWORD: postgres | ||||
|   commands: | ||||
|   - id | ||||
|   - psql --version | ||||
|   - sleep 5 | ||||
|   - pg_isready -h database -U postgres -d dspacestatistics | ||||
|   - createuser -h database -U postgres dspacestatistics | ||||
|   - psql -h database -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'" | ||||
|   - psql -h database -U postgres -d dspacestatistics < tests/dspacestatistics.sql | ||||
|  | ||||
| - name: test | ||||
|   image: python:3.7-slim | ||||
|   environment: | ||||
|     PGPASSWORD: dspacestatistics | ||||
|     DATABASE_HOST: database | ||||
|   commands: | ||||
|   - id | ||||
|   - python -V | ||||
|   - pip install -r requirements-dev.txt | ||||
|   - pytest | ||||
|  | ||||
| --- | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: python36 | ||||
|  | ||||
| steps: | ||||
| - name: database | ||||
|   image: postgres:10-alpine | ||||
|   detach: true | ||||
|   environment: | ||||
|     POSTGRES_USER: postgres | ||||
|     POSTGRES_PASSWORD: postgres | ||||
|     POSTGRES_DB: dspacestatistics | ||||
|  | ||||
| - name: setup | ||||
|   image: postgres:10-alpine | ||||
|   environment: | ||||
|     PGPASSWORD: postgres | ||||
|   commands: | ||||
|   - id | ||||
|   - psql --version | ||||
|   - sleep 5 | ||||
|   - pg_isready -h database -U postgres -d dspacestatistics | ||||
|   - createuser -h database -U postgres dspacestatistics | ||||
|   - psql -h database -U postgres -c "ALTER USER dspacestatistics WITH PASSWORD 'dspacestatistics'" | ||||
|   - psql -h database -U postgres -d dspacestatistics < tests/dspacestatistics.sql | ||||
|  | ||||
| - name: test | ||||
|   image: python:3.6-slim | ||||
|   environment: | ||||
|     PGPASSWORD: dspacestatistics | ||||
|     DATABASE_HOST: database | ||||
|   commands: | ||||
|   - id | ||||
|   - python -V | ||||
|   - pip install -r requirements-dev.txt | ||||
|   - pytest | ||||
|  | ||||
| # vim: ts=2 sw=2 et | ||||
							
								
								
									
										4
									
								
								.hound.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.hound.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| flake8: | ||||
|     enabled: true | ||||
|     config_file: .flake8 | ||||
|     fail_on_violations: true | ||||
							
								
								
									
										225
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,225 @@ | ||||
| # Changelog | ||||
| 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/), | ||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||
|  | ||||
| ## Unreleased | ||||
| ### Changed | ||||
| - Add ORDER BY to /items resource to make sure results are returned | ||||
| deterministically | ||||
|  | ||||
| ## [1.3.2] - 2020-11-18 | ||||
| ### Fixed | ||||
| - Minor issue with limit parameter (> 0) | ||||
| - Minor issue with limit parameter (<= 100) | ||||
|  | ||||
| ### Changed | ||||
| - Minor refactor in Solr bot filtering | ||||
|  | ||||
| ### Updated | ||||
| - Run poetry update | ||||
|  | ||||
| ## [1.3.1] - 2020-10-06 | ||||
| ### Changed | ||||
| - Fix issue with requirements.txt caused by poetry's export | ||||
|  | ||||
| ## [1.3.0] - 2020-10-06 | ||||
| ### Changed | ||||
| - Minor refactoring of indexer | ||||
|  | ||||
| ### Added | ||||
| - Ability to get statistics for arbitrary items and date ranges by POSTing a JSON-formatted request to /items as opposed to the current `GET /items` which returns pre-indexed all-time stats for all items | ||||
|  | ||||
| ### Updated | ||||
| - Run pipenv update, bringing minor updates to pytest, psycopg2-binary, etc | ||||
|  | ||||
| ## [1.2.1] - 2020-03-02 | ||||
| ### Changed | ||||
| - Help text in API docs should reference UUIDs | ||||
| - Sample SQL file for tests should use UUIDs | ||||
|  | ||||
| ## [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 dropping the statistics database and re-indexing | ||||
|  | ||||
| ### Updated | ||||
| - Run pipenv update, bringing requests 2.23.0 and pytest 5.3.5 | ||||
|  | ||||
| ## [1.1.1] - 2019-11-27 | ||||
| ### Added | ||||
| - Configuration for automatic sorting of imports with isort | ||||
| - Configuration for automatic code formatting with black | ||||
|  | ||||
| ### Updated | ||||
| - Run pipenv update, bringing psycopg2 2.8.4, requests 2.22.0, pytest 5.3.1, | ||||
| and gunicorn 20.0.4 | ||||
|  | ||||
| ### Changed | ||||
| - Use Ubuntu 18.04 "Bionic" for TravisCI builds | ||||
| - Use Python 3.8.0 for pipenv | ||||
| - Minor syntax issues highlighted by flake8 | ||||
|  | ||||
| ## [1.1.0] - 2019-05-05 | ||||
| ## Updated | ||||
| - Falcon 2.0.0 (@alanorth) | ||||
|  | ||||
| ## [1.0.0] - 2019-04-15 | ||||
| ### Added | ||||
| - Build configuration for build.sr.ht | ||||
|  | ||||
| ### Updated | ||||
| - Run pipenv update, bringing pytest version 4.4.0, psycopg-binary 2.8.2, etc | ||||
| - sr.ht and TravisCI configuration to disable emojis and animation to keep logs clean | ||||
|  | ||||
| ### Changed | ||||
| - Use vanilla requests library instead of SolrClient | ||||
| - Use one-based paging in indexer output (for human readability) | ||||
|  | ||||
| ## [0.9.0] - 2019-01-22 | ||||
| ### Updated | ||||
| - pytest version 4.0.0 | ||||
| - Fix indexing of sharded statistics cores ([#10)) | ||||
| - Handle case of missing views/downloads gracefully | ||||
|  | ||||
| ## [0.8.1] - 2018-11-14 | ||||
| ### Changed | ||||
| - README.md to recommend using vanilla Python virtual environments and pip instead of pipenv | ||||
| - Regenerate pipenv environment to capture only direct dependencies | ||||
|  | ||||
| ### Added | ||||
| - `requirements-dev.txt` for installing development packages with pip | ||||
|  | ||||
| ## [0.8.0] - 2018-11-11 | ||||
| ### 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) | ||||
| - Hound CI configuration to validate pull requests against PEP 8 code style with Flake8 | ||||
| - Configuration for [pipenv](https://pipenv.readthedocs.io/en/latest/) | ||||
|  | ||||
| ### Changed | ||||
| - Use a database management class with Python context management to automatically open/close connections and cursors | ||||
|  | ||||
| ### Changed | ||||
| - Validate code against PEP 8 style guide with Flake8 | ||||
|  | ||||
| ## [0.6.1] - 2018-10-31 | ||||
| ### Added | ||||
| - API documentation at root path (/) | ||||
|  | ||||
| ## [0.6.0] - 2018-10-31 | ||||
| ### Changed | ||||
| - Refactor project structure (note breaking changes to API and indexing invocation, see contrib and README.md) | ||||
|  | ||||
| ## [0.5.2] - 2018-10-28 | ||||
| ### Changed | ||||
| - Update library versions in requirements.txt | ||||
|  | ||||
| ## [0.5.1] - 2018-10-24 | ||||
| ### Changed | ||||
| - Use Python's native json instead of ujson | ||||
|  | ||||
| ## [0.5.0] - 2018-10-24 | ||||
| ### Added | ||||
| - Example nginx configuration to README.md | ||||
|  | ||||
| ### Changed | ||||
| - Don't initialize Solr connection in API | ||||
|  | ||||
| ## [0.4.3] - 2018-10-17 | ||||
| ### Changed | ||||
| - Use pip install as script for Travis CI | ||||
|  | ||||
| ### Improved | ||||
| - Documentation for deployment and testing | ||||
|  | ||||
| ## [0.4.2] - 2018-10-04 | ||||
| ### Changed | ||||
| - README.md introduction and requirements | ||||
| - Use ujson instead of json | ||||
| - Iterate directly on SQL cursor in `/items` route | ||||
|  | ||||
| ### Fixed | ||||
| - Logic error in SQL for item views | ||||
|  | ||||
| ## [0.4.1] - 2018-09-26 | ||||
| ### Changed | ||||
| - Use `execute_values()` to batch insert records to PostgreSQL | ||||
|  | ||||
| ## [0.4.0] - 2018-09-25 | ||||
| ### Fixed | ||||
| - Invalid OnCalendar syntax in dspace-statistics-indexer.timer | ||||
| - Major logic error in indexer.py | ||||
|  | ||||
| ## [0.3.2] - 2018-09-25 | ||||
| ## Changed | ||||
| - /item/id route now returns HTTP 404 if an item is not found | ||||
|  | ||||
| ## [0.3.1] - 2018-09-25 | ||||
| ### Changed | ||||
| - Force SolrClient's kazoo dependency to version 2.5.0 to work with Python 3.7 | ||||
| - Add Python 3.7 to Travis CI configuration | ||||
|  | ||||
| ## [0.3.0] - 2018-09-25 | ||||
| ### Added | ||||
| - requirements.txt for pip | ||||
| - Travis CI build configuration for Python 3.5 and 3.6 | ||||
| - Documentation on using the API | ||||
|  | ||||
| ### Changed | ||||
| - The "all items" route from / to /items | ||||
|  | ||||
| ## [0.2.1] - 2018-09-24 | ||||
| ### Changed | ||||
| - Environment settings in example systemd unit files | ||||
| - Use psycopg2.extras.DictCursor for PostgreSQL connection | ||||
|  | ||||
| ## [0.2.0] - 2018-09-24 | ||||
| ### Changed | ||||
| - Use PostgreSQL instead of SQLite because UPSERT support needs a very new libsqlite3 whereas it's already in PostgreSQL 9.5+ | ||||
|  | ||||
| ## [0.1.0] - 2018-09-24 | ||||
| ### Changed | ||||
| - Rename project to "DSpace Statistics API" | ||||
| - Use read-only database connection in API | ||||
| - Update systemd units for CGSpace→DSpace rename | ||||
| - Use UPSERT to simplify database schema and Python logic | ||||
|  | ||||
| ### Added | ||||
| - Example systemd service and timer unit for indexer service | ||||
| - Add top-level route to expose all item statistics | ||||
|  | ||||
| ### Removed | ||||
| - Ability to customize SOLR_CORE variable | ||||
|  | ||||
| ## [0.0.4] - 2018-09-23 | ||||
| ### Added | ||||
| - Added example systemd unit file for API | ||||
| - Added indexer.py to ingest views and downloads from Solr to a SQLite database | ||||
|  | ||||
| ### Changed | ||||
| - Refactor Solr configuration and connection | ||||
| - /item route now expects id as part of the URI instead of a query parameter: /item/id | ||||
| - View and download stats are now fetched from a SQLite database | ||||
|  | ||||
| ## [0.0.3] - 2018-09-20 | ||||
| ### Changed | ||||
| - Refactor environment variables into config module | ||||
| - Simplify Solr query for "downloads" | ||||
| - Optimize Solr query by using rows=0 | ||||
| - Fix Solr queries for item views | ||||
|  | ||||
| ## [0.0.2] - 2018-09-18 | ||||
| ### Added | ||||
| - Ability to get Solr parameters from environment (`SOLR_SERVER` and `SOLR_CORE`) | ||||
|  | ||||
| ## [0.0.1] - 2018-09-18 | ||||
| - Initial release | ||||
							
								
								
									
										674
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										674
									
								
								LICENSE.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,674 @@ | ||||
|                     GNU GENERAL PUBLIC LICENSE | ||||
|                        Version 3, 29 June 2007 | ||||
|  | ||||
|  Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> | ||||
|  Everyone is permitted to copy and distribute verbatim copies | ||||
|  of this license document, but changing it is not allowed. | ||||
|  | ||||
|                             Preamble | ||||
|  | ||||
|   The GNU General Public License is a free, copyleft license for | ||||
| software and other kinds of works. | ||||
|  | ||||
|   The licenses for most software and other practical works are designed | ||||
| to take away your freedom to share and change the works.  By contrast, | ||||
| the GNU General Public License is intended to guarantee your freedom to | ||||
| share and change all versions of a program--to make sure it remains free | ||||
| software for all its users.  We, the Free Software Foundation, use the | ||||
| GNU General Public License for most of our software; it applies also to | ||||
| any other work released this way by its authors.  You can apply it to | ||||
| your programs, too. | ||||
|  | ||||
|   When we speak of free software, we are referring to freedom, not | ||||
| price.  Our General Public Licenses are designed to make sure that you | ||||
| have the freedom to distribute copies of free software (and charge for | ||||
| them if you wish), that you receive source code or can get it if you | ||||
| want it, that you can change the software or use pieces of it in new | ||||
| free programs, and that you know you can do these things. | ||||
|  | ||||
|   To protect your rights, we need to prevent others from denying you | ||||
| these rights or asking you to surrender the rights.  Therefore, you have | ||||
| certain responsibilities if you distribute copies of the software, or if | ||||
| you modify it: responsibilities to respect the freedom of others. | ||||
|  | ||||
|   For example, if you distribute copies of such a program, whether | ||||
| gratis or for a fee, you must pass on to the recipients the same | ||||
| freedoms that you received.  You must make sure that they, too, receive | ||||
| or can get the source code.  And you must show them these terms so they | ||||
| know their rights. | ||||
|  | ||||
|   Developers that use the GNU GPL protect your rights with two steps: | ||||
| (1) assert copyright on the software, and (2) offer you this License | ||||
| giving you legal permission to copy, distribute and/or modify it. | ||||
|  | ||||
|   For the developers' and authors' protection, the GPL clearly explains | ||||
| that there is no warranty for this free software.  For both users' and | ||||
| authors' sake, the GPL requires that modified versions be marked as | ||||
| changed, so that their problems will not be attributed erroneously to | ||||
| authors of previous versions. | ||||
|  | ||||
|   Some devices are designed to deny users access to install or run | ||||
| modified versions of the software inside them, although the manufacturer | ||||
| can do so.  This is fundamentally incompatible with the aim of | ||||
| protecting users' freedom to change the software.  The systematic | ||||
| pattern of such abuse occurs in the area of products for individuals to | ||||
| use, which is precisely where it is most unacceptable.  Therefore, we | ||||
| have designed this version of the GPL to prohibit the practice for those | ||||
| products.  If such problems arise substantially in other domains, we | ||||
| stand ready to extend this provision to those domains in future versions | ||||
| of the GPL, as needed to protect the freedom of users. | ||||
|  | ||||
|   Finally, every program is threatened constantly by software patents. | ||||
| States should not allow patents to restrict development and use of | ||||
| software on general-purpose computers, but in those that do, we wish to | ||||
| avoid the special danger that patents applied to a free program could | ||||
| make it effectively proprietary.  To prevent this, the GPL assures that | ||||
| patents cannot be used to render the program non-free. | ||||
|  | ||||
|   The precise terms and conditions for copying, distribution and | ||||
| modification follow. | ||||
|  | ||||
|                        TERMS AND CONDITIONS | ||||
|  | ||||
|   0. Definitions. | ||||
|  | ||||
|   "This License" refers to version 3 of the GNU General Public License. | ||||
|  | ||||
|   "Copyright" also means copyright-like laws that apply to other kinds of | ||||
| works, such as semiconductor masks. | ||||
|  | ||||
|   "The Program" refers to any copyrightable work licensed under this | ||||
| License.  Each licensee is addressed as "you".  "Licensees" and | ||||
| "recipients" may be individuals or organizations. | ||||
|  | ||||
|   To "modify" a work means to copy from or adapt all or part of the work | ||||
| in a fashion requiring copyright permission, other than the making of an | ||||
| exact copy.  The resulting work is called a "modified version" of the | ||||
| earlier work or a work "based on" the earlier work. | ||||
|  | ||||
|   A "covered work" means either the unmodified Program or a work based | ||||
| on the Program. | ||||
|  | ||||
|   To "propagate" a work means to do anything with it that, without | ||||
| permission, would make you directly or secondarily liable for | ||||
| infringement under applicable copyright law, except executing it on a | ||||
| computer or modifying a private copy.  Propagation includes copying, | ||||
| distribution (with or without modification), making available to the | ||||
| public, and in some countries other activities as well. | ||||
|  | ||||
|   To "convey" a work means any kind of propagation that enables other | ||||
| parties to make or receive copies.  Mere interaction with a user through | ||||
| a computer network, with no transfer of a copy, is not conveying. | ||||
|  | ||||
|   An interactive user interface displays "Appropriate Legal Notices" | ||||
| to the extent that it includes a convenient and prominently visible | ||||
| feature that (1) displays an appropriate copyright notice, and (2) | ||||
| tells the user that there is no warranty for the work (except to the | ||||
| extent that warranties are provided), that licensees may convey the | ||||
| work under this License, and how to view a copy of this License.  If | ||||
| the interface presents a list of user commands or options, such as a | ||||
| menu, a prominent item in the list meets this criterion. | ||||
|  | ||||
|   1. Source Code. | ||||
|  | ||||
|   The "source code" for a work means the preferred form of the work | ||||
| for making modifications to it.  "Object code" means any non-source | ||||
| form of a work. | ||||
|  | ||||
|   A "Standard Interface" means an interface that either is an official | ||||
| standard defined by a recognized standards body, or, in the case of | ||||
| interfaces specified for a particular programming language, one that | ||||
| is widely used among developers working in that language. | ||||
|  | ||||
|   The "System Libraries" of an executable work include anything, other | ||||
| than the work as a whole, that (a) is included in the normal form of | ||||
| packaging a Major Component, but which is not part of that Major | ||||
| Component, and (b) serves only to enable use of the work with that | ||||
| Major Component, or to implement a Standard Interface for which an | ||||
| implementation is available to the public in source code form.  A | ||||
| "Major Component", in this context, means a major essential component | ||||
| (kernel, window system, and so on) of the specific operating system | ||||
| (if any) on which the executable work runs, or a compiler used to | ||||
| produce the work, or an object code interpreter used to run it. | ||||
|  | ||||
|   The "Corresponding Source" for a work in object code form means all | ||||
| the source code needed to generate, install, and (for an executable | ||||
| work) run the object code and to modify the work, including scripts to | ||||
| control those activities.  However, it does not include the work's | ||||
| System Libraries, or general-purpose tools or generally available free | ||||
| programs which are used unmodified in performing those activities but | ||||
| which are not part of the work.  For example, Corresponding Source | ||||
| includes interface definition files associated with source files for | ||||
| the work, and the source code for shared libraries and dynamically | ||||
| linked subprograms that the work is specifically designed to require, | ||||
| such as by intimate data communication or control flow between those | ||||
| subprograms and other parts of the work. | ||||
|  | ||||
|   The Corresponding Source need not include anything that users | ||||
| can regenerate automatically from other parts of the Corresponding | ||||
| Source. | ||||
|  | ||||
|   The Corresponding Source for a work in source code form is that | ||||
| same work. | ||||
|  | ||||
|   2. Basic Permissions. | ||||
|  | ||||
|   All rights granted under this License are granted for the term of | ||||
| copyright on the Program, and are irrevocable provided the stated | ||||
| conditions are met.  This License explicitly affirms your unlimited | ||||
| permission to run the unmodified Program.  The output from running a | ||||
| covered work is covered by this License only if the output, given its | ||||
| content, constitutes a covered work.  This License acknowledges your | ||||
| rights of fair use or other equivalent, as provided by copyright law. | ||||
|  | ||||
|   You may make, run and propagate covered works that you do not | ||||
| convey, without conditions so long as your license otherwise remains | ||||
| in force.  You may convey covered works to others for the sole purpose | ||||
| of having them make modifications exclusively for you, or provide you | ||||
| with facilities for running those works, provided that you comply with | ||||
| the terms of this License in conveying all material for which you do | ||||
| not control copyright.  Those thus making or running the covered works | ||||
| for you must do so exclusively on your behalf, under your direction | ||||
| and control, on terms that prohibit them from making any copies of | ||||
| your copyrighted material outside their relationship with you. | ||||
|  | ||||
|   Conveying under any other circumstances is permitted solely under | ||||
| the conditions stated below.  Sublicensing is not allowed; section 10 | ||||
| makes it unnecessary. | ||||
|  | ||||
|   3. Protecting Users' Legal Rights From Anti-Circumvention Law. | ||||
|  | ||||
|   No covered work shall be deemed part of an effective technological | ||||
| measure under any applicable law fulfilling obligations under article | ||||
| 11 of the WIPO copyright treaty adopted on 20 December 1996, or | ||||
| similar laws prohibiting or restricting circumvention of such | ||||
| measures. | ||||
|  | ||||
|   When you convey a covered work, you waive any legal power to forbid | ||||
| circumvention of technological measures to the extent such circumvention | ||||
| is effected by exercising rights under this License with respect to | ||||
| the covered work, and you disclaim any intention to limit operation or | ||||
| modification of the work as a means of enforcing, against the work's | ||||
| users, your or third parties' legal rights to forbid circumvention of | ||||
| technological measures. | ||||
|  | ||||
|   4. Conveying Verbatim Copies. | ||||
|  | ||||
|   You may convey verbatim copies of the Program's source code as you | ||||
| receive it, in any medium, provided that you conspicuously and | ||||
| appropriately publish on each copy an appropriate copyright notice; | ||||
| keep intact all notices stating that this License and any | ||||
| non-permissive terms added in accord with section 7 apply to the code; | ||||
| keep intact all notices of the absence of any warranty; and give all | ||||
| recipients a copy of this License along with the Program. | ||||
|  | ||||
|   You may charge any price or no price for each copy that you convey, | ||||
| and you may offer support or warranty protection for a fee. | ||||
|  | ||||
|   5. Conveying Modified Source Versions. | ||||
|  | ||||
|   You may convey a work based on the Program, or the modifications to | ||||
| produce it from the Program, in the form of source code under the | ||||
| terms of section 4, provided that you also meet all of these conditions: | ||||
|  | ||||
|     a) The work must carry prominent notices stating that you modified | ||||
|     it, and giving a relevant date. | ||||
|  | ||||
|     b) The work must carry prominent notices stating that it is | ||||
|     released under this License and any conditions added under section | ||||
|     7.  This requirement modifies the requirement in section 4 to | ||||
|     "keep intact all notices". | ||||
|  | ||||
|     c) You must license the entire work, as a whole, under this | ||||
|     License to anyone who comes into possession of a copy.  This | ||||
|     License will therefore apply, along with any applicable section 7 | ||||
|     additional terms, to the whole of the work, and all its parts, | ||||
|     regardless of how they are packaged.  This License gives no | ||||
|     permission to license the work in any other way, but it does not | ||||
|     invalidate such permission if you have separately received it. | ||||
|  | ||||
|     d) If the work has interactive user interfaces, each must display | ||||
|     Appropriate Legal Notices; however, if the Program has interactive | ||||
|     interfaces that do not display Appropriate Legal Notices, your | ||||
|     work need not make them do so. | ||||
|  | ||||
|   A compilation of a covered work with other separate and independent | ||||
| works, which are not by their nature extensions of the covered work, | ||||
| and which are not combined with it such as to form a larger program, | ||||
| in or on a volume of a storage or distribution medium, is called an | ||||
| "aggregate" if the compilation and its resulting copyright are not | ||||
| used to limit the access or legal rights of the compilation's users | ||||
| beyond what the individual works permit.  Inclusion of a covered work | ||||
| in an aggregate does not cause this License to apply to the other | ||||
| parts of the aggregate. | ||||
|  | ||||
|   6. Conveying Non-Source Forms. | ||||
|  | ||||
|   You may convey a covered work in object code form under the terms | ||||
| of sections 4 and 5, provided that you also convey the | ||||
| machine-readable Corresponding Source under the terms of this License, | ||||
| in one of these ways: | ||||
|  | ||||
|     a) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by the | ||||
|     Corresponding Source fixed on a durable physical medium | ||||
|     customarily used for software interchange. | ||||
|  | ||||
|     b) Convey the object code in, or embodied in, a physical product | ||||
|     (including a physical distribution medium), accompanied by a | ||||
|     written offer, valid for at least three years and valid for as | ||||
|     long as you offer spare parts or customer support for that product | ||||
|     model, to give anyone who possesses the object code either (1) a | ||||
|     copy of the Corresponding Source for all the software in the | ||||
|     product that is covered by this License, on a durable physical | ||||
|     medium customarily used for software interchange, for a price no | ||||
|     more than your reasonable cost of physically performing this | ||||
|     conveying of source, or (2) access to copy the | ||||
|     Corresponding Source from a network server at no charge. | ||||
|  | ||||
|     c) Convey individual copies of the object code with a copy of the | ||||
|     written offer to provide the Corresponding Source.  This | ||||
|     alternative is allowed only occasionally and noncommercially, and | ||||
|     only if you received the object code with such an offer, in accord | ||||
|     with subsection 6b. | ||||
|  | ||||
|     d) Convey the object code by offering access from a designated | ||||
|     place (gratis or for a charge), and offer equivalent access to the | ||||
|     Corresponding Source in the same way through the same place at no | ||||
|     further charge.  You need not require recipients to copy the | ||||
|     Corresponding Source along with the object code.  If the place to | ||||
|     copy the object code is a network server, the Corresponding Source | ||||
|     may be on a different server (operated by you or a third party) | ||||
|     that supports equivalent copying facilities, provided you maintain | ||||
|     clear directions next to the object code saying where to find the | ||||
|     Corresponding Source.  Regardless of what server hosts the | ||||
|     Corresponding Source, you remain obligated to ensure that it is | ||||
|     available for as long as needed to satisfy these requirements. | ||||
|  | ||||
|     e) Convey the object code using peer-to-peer transmission, provided | ||||
|     you inform other peers where the object code and Corresponding | ||||
|     Source of the work are being offered to the general public at no | ||||
|     charge under subsection 6d. | ||||
|  | ||||
|   A separable portion of the object code, whose source code is excluded | ||||
| from the Corresponding Source as a System Library, need not be | ||||
| included in conveying the object code work. | ||||
|  | ||||
|   A "User Product" is either (1) a "consumer product", which means any | ||||
| tangible personal property which is normally used for personal, family, | ||||
| or household purposes, or (2) anything designed or sold for incorporation | ||||
| into a dwelling.  In determining whether a product is a consumer product, | ||||
| doubtful cases shall be resolved in favor of coverage.  For a particular | ||||
| product received by a particular user, "normally used" refers to a | ||||
| typical or common use of that class of product, regardless of the status | ||||
| of the particular user or of the way in which the particular user | ||||
| actually uses, or expects or is expected to use, the product.  A product | ||||
| is a consumer product regardless of whether the product has substantial | ||||
| commercial, industrial or non-consumer uses, unless such uses represent | ||||
| the only significant mode of use of the product. | ||||
|  | ||||
|   "Installation Information" for a User Product means any methods, | ||||
| procedures, authorization keys, or other information required to install | ||||
| and execute modified versions of a covered work in that User Product from | ||||
| a modified version of its Corresponding Source.  The information must | ||||
| suffice to ensure that the continued functioning of the modified object | ||||
| code is in no case prevented or interfered with solely because | ||||
| modification has been made. | ||||
|  | ||||
|   If you convey an object code work under this section in, or with, or | ||||
| specifically for use in, a User Product, and the conveying occurs as | ||||
| part of a transaction in which the right of possession and use of the | ||||
| User Product is transferred to the recipient in perpetuity or for a | ||||
| fixed term (regardless of how the transaction is characterized), the | ||||
| Corresponding Source conveyed under this section must be accompanied | ||||
| by the Installation Information.  But this requirement does not apply | ||||
| if neither you nor any third party retains the ability to install | ||||
| modified object code on the User Product (for example, the work has | ||||
| been installed in ROM). | ||||
|  | ||||
|   The requirement to provide Installation Information does not include a | ||||
| requirement to continue to provide support service, warranty, or updates | ||||
| for a work that has been modified or installed by the recipient, or for | ||||
| the User Product in which it has been modified or installed.  Access to a | ||||
| network may be denied when the modification itself materially and | ||||
| adversely affects the operation of the network or violates the rules and | ||||
| protocols for communication across the network. | ||||
|  | ||||
|   Corresponding Source conveyed, and Installation Information provided, | ||||
| in accord with this section must be in a format that is publicly | ||||
| documented (and with an implementation available to the public in | ||||
| source code form), and must require no special password or key for | ||||
| unpacking, reading or copying. | ||||
|  | ||||
|   7. Additional Terms. | ||||
|  | ||||
|   "Additional permissions" are terms that supplement the terms of this | ||||
| License by making exceptions from one or more of its conditions. | ||||
| Additional permissions that are applicable to the entire Program shall | ||||
| be treated as though they were included in this License, to the extent | ||||
| that they are valid under applicable law.  If additional permissions | ||||
| apply only to part of the Program, that part may be used separately | ||||
| under those permissions, but the entire Program remains governed by | ||||
| this License without regard to the additional permissions. | ||||
|  | ||||
|   When you convey a copy of a covered work, you may at your option | ||||
| remove any additional permissions from that copy, or from any part of | ||||
| it.  (Additional permissions may be written to require their own | ||||
| removal in certain cases when you modify the work.)  You may place | ||||
| additional permissions on material, added by you to a covered work, | ||||
| for which you have or can give appropriate copyright permission. | ||||
|  | ||||
|   Notwithstanding any other provision of this License, for material you | ||||
| add to a covered work, you may (if authorized by the copyright holders of | ||||
| that material) supplement the terms of this License with terms: | ||||
|  | ||||
|     a) Disclaiming warranty or limiting liability differently from the | ||||
|     terms of sections 15 and 16 of this License; or | ||||
|  | ||||
|     b) Requiring preservation of specified reasonable legal notices or | ||||
|     author attributions in that material or in the Appropriate Legal | ||||
|     Notices displayed by works containing it; or | ||||
|  | ||||
|     c) Prohibiting misrepresentation of the origin of that material, or | ||||
|     requiring that modified versions of such material be marked in | ||||
|     reasonable ways as different from the original version; or | ||||
|  | ||||
|     d) Limiting the use for publicity purposes of names of licensors or | ||||
|     authors of the material; or | ||||
|  | ||||
|     e) Declining to grant rights under trademark law for use of some | ||||
|     trade names, trademarks, or service marks; or | ||||
|  | ||||
|     f) Requiring indemnification of licensors and authors of that | ||||
|     material by anyone who conveys the material (or modified versions of | ||||
|     it) with contractual assumptions of liability to the recipient, for | ||||
|     any liability that these contractual assumptions directly impose on | ||||
|     those licensors and authors. | ||||
|  | ||||
|   All other non-permissive additional terms are considered "further | ||||
| restrictions" within the meaning of section 10.  If the Program as you | ||||
| received it, or any part of it, contains a notice stating that it is | ||||
| governed by this License along with a term that is a further | ||||
| restriction, you may remove that term.  If a license document contains | ||||
| a further restriction but permits relicensing or conveying under this | ||||
| License, you may add to a covered work material governed by the terms | ||||
| of that license document, provided that the further restriction does | ||||
| not survive such relicensing or conveying. | ||||
|  | ||||
|   If you add terms to a covered work in accord with this section, you | ||||
| must place, in the relevant source files, a statement of the | ||||
| additional terms that apply to those files, or a notice indicating | ||||
| where to find the applicable terms. | ||||
|  | ||||
|   Additional terms, permissive or non-permissive, may be stated in the | ||||
| form of a separately written license, or stated as exceptions; | ||||
| the above requirements apply either way. | ||||
|  | ||||
|   8. Termination. | ||||
|  | ||||
|   You may not propagate or modify a covered work except as expressly | ||||
| provided under this License.  Any attempt otherwise to propagate or | ||||
| modify it is void, and will automatically terminate your rights under | ||||
| this License (including any patent licenses granted under the third | ||||
| paragraph of section 11). | ||||
|  | ||||
|   However, if you cease all violation of this License, then your | ||||
| license from a particular copyright holder is reinstated (a) | ||||
| provisionally, unless and until the copyright holder explicitly and | ||||
| finally terminates your license, and (b) permanently, if the copyright | ||||
| holder fails to notify you of the violation by some reasonable means | ||||
| prior to 60 days after the cessation. | ||||
|  | ||||
|   Moreover, your license from a particular copyright holder is | ||||
| reinstated permanently if the copyright holder notifies you of the | ||||
| violation by some reasonable means, this is the first time you have | ||||
| received notice of violation of this License (for any work) from that | ||||
| copyright holder, and you cure the violation prior to 30 days after | ||||
| your receipt of the notice. | ||||
|  | ||||
|   Termination of your rights under this section does not terminate the | ||||
| licenses of parties who have received copies or rights from you under | ||||
| this License.  If your rights have been terminated and not permanently | ||||
| reinstated, you do not qualify to receive new licenses for the same | ||||
| material under section 10. | ||||
|  | ||||
|   9. Acceptance Not Required for Having Copies. | ||||
|  | ||||
|   You are not required to accept this License in order to receive or | ||||
| run a copy of the Program.  Ancillary propagation of a covered work | ||||
| occurring solely as a consequence of using peer-to-peer transmission | ||||
| to receive a copy likewise does not require acceptance.  However, | ||||
| nothing other than this License grants you permission to propagate or | ||||
| modify any covered work.  These actions infringe copyright if you do | ||||
| not accept this License.  Therefore, by modifying or propagating a | ||||
| covered work, you indicate your acceptance of this License to do so. | ||||
|  | ||||
|   10. Automatic Licensing of Downstream Recipients. | ||||
|  | ||||
|   Each time you convey a covered work, the recipient automatically | ||||
| receives a license from the original licensors, to run, modify and | ||||
| propagate that work, subject to this License.  You are not responsible | ||||
| for enforcing compliance by third parties with this License. | ||||
|  | ||||
|   An "entity transaction" is a transaction transferring control of an | ||||
| organization, or substantially all assets of one, or subdividing an | ||||
| organization, or merging organizations.  If propagation of a covered | ||||
| work results from an entity transaction, each party to that | ||||
| transaction who receives a copy of the work also receives whatever | ||||
| licenses to the work the party's predecessor in interest had or could | ||||
| give under the previous paragraph, plus a right to possession of the | ||||
| Corresponding Source of the work from the predecessor in interest, if | ||||
| the predecessor has it or can get it with reasonable efforts. | ||||
|  | ||||
|   You may not impose any further restrictions on the exercise of the | ||||
| rights granted or affirmed under this License.  For example, you may | ||||
| not impose a license fee, royalty, or other charge for exercise of | ||||
| rights granted under this License, and you may not initiate litigation | ||||
| (including a cross-claim or counterclaim in a lawsuit) alleging that | ||||
| any patent claim is infringed by making, using, selling, offering for | ||||
| sale, or importing the Program or any portion of it. | ||||
|  | ||||
|   11. Patents. | ||||
|  | ||||
|   A "contributor" is a copyright holder who authorizes use under this | ||||
| License of the Program or a work on which the Program is based.  The | ||||
| work thus licensed is called the contributor's "contributor version". | ||||
|  | ||||
|   A contributor's "essential patent claims" are all patent claims | ||||
| owned or controlled by the contributor, whether already acquired or | ||||
| hereafter acquired, that would be infringed by some manner, permitted | ||||
| by this License, of making, using, or selling its contributor version, | ||||
| but do not include claims that would be infringed only as a | ||||
| consequence of further modification of the contributor version.  For | ||||
| purposes of this definition, "control" includes the right to grant | ||||
| patent sublicenses in a manner consistent with the requirements of | ||||
| this License. | ||||
|  | ||||
|   Each contributor grants you a non-exclusive, worldwide, royalty-free | ||||
| patent license under the contributor's essential patent claims, to | ||||
| make, use, sell, offer for sale, import and otherwise run, modify and | ||||
| propagate the contents of its contributor version. | ||||
|  | ||||
|   In the following three paragraphs, a "patent license" is any express | ||||
| agreement or commitment, however denominated, not to enforce a patent | ||||
| (such as an express permission to practice a patent or covenant not to | ||||
| sue for patent infringement).  To "grant" such a patent license to a | ||||
| party means to make such an agreement or commitment not to enforce a | ||||
| patent against the party. | ||||
|  | ||||
|   If you convey a covered work, knowingly relying on a patent license, | ||||
| and the Corresponding Source of the work is not available for anyone | ||||
| to copy, free of charge and under the terms of this License, through a | ||||
| publicly available network server or other readily accessible means, | ||||
| then you must either (1) cause the Corresponding Source to be so | ||||
| available, or (2) arrange to deprive yourself of the benefit of the | ||||
| patent license for this particular work, or (3) arrange, in a manner | ||||
| consistent with the requirements of this License, to extend the patent | ||||
| license to downstream recipients.  "Knowingly relying" means you have | ||||
| actual knowledge that, but for the patent license, your conveying the | ||||
| covered work in a country, or your recipient's use of the covered work | ||||
| in a country, would infringe one or more identifiable patents in that | ||||
| country that you have reason to believe are valid. | ||||
|  | ||||
|   If, pursuant to or in connection with a single transaction or | ||||
| arrangement, you convey, or propagate by procuring conveyance of, a | ||||
| covered work, and grant a patent license to some of the parties | ||||
| receiving the covered work authorizing them to use, propagate, modify | ||||
| or convey a specific copy of the covered work, then the patent license | ||||
| you grant is automatically extended to all recipients of the covered | ||||
| work and works based on it. | ||||
|  | ||||
|   A patent license is "discriminatory" if it does not include within | ||||
| the scope of its coverage, prohibits the exercise of, or is | ||||
| conditioned on the non-exercise of one or more of the rights that are | ||||
| specifically granted under this License.  You may not convey a covered | ||||
| work if you are a party to an arrangement with a third party that is | ||||
| in the business of distributing software, under which you make payment | ||||
| to the third party based on the extent of your activity of conveying | ||||
| the work, and under which the third party grants, to any of the | ||||
| parties who would receive the covered work from you, a discriminatory | ||||
| patent license (a) in connection with copies of the covered work | ||||
| conveyed by you (or copies made from those copies), or (b) primarily | ||||
| for and in connection with specific products or compilations that | ||||
| contain the covered work, unless you entered into that arrangement, | ||||
| or that patent license was granted, prior to 28 March 2007. | ||||
|  | ||||
|   Nothing in this License shall be construed as excluding or limiting | ||||
| any implied license or other defenses to infringement that may | ||||
| otherwise be available to you under applicable patent law. | ||||
|  | ||||
|   12. No Surrender of Others' Freedom. | ||||
|  | ||||
|   If conditions are imposed on you (whether by court order, agreement or | ||||
| otherwise) that contradict the conditions of this License, they do not | ||||
| excuse you from the conditions of this License.  If you cannot convey a | ||||
| covered work so as to satisfy simultaneously your obligations under this | ||||
| License and any other pertinent obligations, then as a consequence you may | ||||
| not convey it at all.  For example, if you agree to terms that obligate you | ||||
| to collect a royalty for further conveying from those to whom you convey | ||||
| the Program, the only way you could satisfy both those terms and this | ||||
| License would be to refrain entirely from conveying the Program. | ||||
|  | ||||
|   13. Use with the GNU Affero General Public License. | ||||
|  | ||||
|   Notwithstanding any other provision of this License, you have | ||||
| permission to link or combine any covered work with a work licensed | ||||
| under version 3 of the GNU Affero General Public License into a single | ||||
| combined work, and to convey the resulting work.  The terms of this | ||||
| License will continue to apply to the part which is the covered work, | ||||
| but the special requirements of the GNU Affero General Public License, | ||||
| section 13, concerning interaction through a network will apply to the | ||||
| combination as such. | ||||
|  | ||||
|   14. Revised Versions of this License. | ||||
|  | ||||
|   The Free Software Foundation may publish revised and/or new versions of | ||||
| the GNU General Public License from time to time.  Such new versions will | ||||
| be similar in spirit to the present version, but may differ in detail to | ||||
| address new problems or concerns. | ||||
|  | ||||
|   Each version is given a distinguishing version number.  If the | ||||
| Program specifies that a certain numbered version of the GNU General | ||||
| Public License "or any later version" applies to it, you have the | ||||
| option of following the terms and conditions either of that numbered | ||||
| version or of any later version published by the Free Software | ||||
| Foundation.  If the Program does not specify a version number of the | ||||
| GNU General Public License, you may choose any version ever published | ||||
| by the Free Software Foundation. | ||||
|  | ||||
|   If the Program specifies that a proxy can decide which future | ||||
| versions of the GNU General Public License can be used, that proxy's | ||||
| public statement of acceptance of a version permanently authorizes you | ||||
| to choose that version for the Program. | ||||
|  | ||||
|   Later license versions may give you additional or different | ||||
| permissions.  However, no additional obligations are imposed on any | ||||
| author or copyright holder as a result of your choosing to follow a | ||||
| later version. | ||||
|  | ||||
|   15. Disclaimer of Warranty. | ||||
|  | ||||
|   THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY | ||||
| APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT | ||||
| HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY | ||||
| OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, | ||||
| THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | ||||
| PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM | ||||
| IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF | ||||
| ALL NECESSARY SERVICING, REPAIR OR CORRECTION. | ||||
|  | ||||
|   16. Limitation of Liability. | ||||
|  | ||||
|   IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING | ||||
| WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS | ||||
| THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY | ||||
| GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE | ||||
| USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF | ||||
| DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD | ||||
| PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), | ||||
| EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF | ||||
| SUCH DAMAGES. | ||||
|  | ||||
|   17. Interpretation of Sections 15 and 16. | ||||
|  | ||||
|   If the disclaimer of warranty and limitation of liability provided | ||||
| above cannot be given local legal effect according to their terms, | ||||
| reviewing courts shall apply local law that most closely approximates | ||||
| an absolute waiver of all civil liability in connection with the | ||||
| Program, unless a warranty or assumption of liability accompanies a | ||||
| copy of the Program in return for a fee. | ||||
|  | ||||
|                      END OF TERMS AND CONDITIONS | ||||
|  | ||||
|             How to Apply These Terms to Your New Programs | ||||
|  | ||||
|   If you develop a new program, and you want it to be of the greatest | ||||
| possible use to the public, the best way to achieve this is to make it | ||||
| free software which everyone can redistribute and change under these terms. | ||||
|  | ||||
|   To do so, attach the following notices to the program.  It is safest | ||||
| to attach them to the start of each source file to most effectively | ||||
| state the exclusion of warranty; and each file should have at least | ||||
| the "copyright" line and a pointer to where the full notice is found. | ||||
|  | ||||
|     <one line to give the program's name and a brief idea of what it does.> | ||||
|     Copyright (C) <year>  <name of author> | ||||
|  | ||||
|     This program is free software: you can redistribute it and/or modify | ||||
|     it under the terms of the GNU General Public License as published by | ||||
|     the Free Software Foundation, either version 3 of the License, or | ||||
|     (at your option) any later version. | ||||
|  | ||||
|     This program is distributed in the hope that it will be useful, | ||||
|     but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|     GNU General Public License for more details. | ||||
|  | ||||
|     You should have received a copy of the GNU General Public License | ||||
|     along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||
|  | ||||
| Also add information on how to contact you by electronic and paper mail. | ||||
|  | ||||
|   If the program does terminal interaction, make it output a short | ||||
| notice like this when it starts in an interactive mode: | ||||
|  | ||||
|     <program>  Copyright (C) <year>  <name of author> | ||||
|     This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. | ||||
|     This is free software, and you are welcome to redistribute it | ||||
|     under certain conditions; type `show c' for details. | ||||
|  | ||||
| The hypothetical commands `show w' and `show c' should show the appropriate | ||||
| parts of the General Public License.  Of course, your program's commands | ||||
| might be different; for a GUI interface, you would use an "about box". | ||||
|  | ||||
|   You should also get your employer (if you work as a programmer) or school, | ||||
| if any, to sign a "copyright disclaimer" for the program, if necessary. | ||||
| For more information on this, and how to apply and follow the GNU GPL, see | ||||
| <https://www.gnu.org/licenses/>. | ||||
|  | ||||
|   The GNU General Public License does not permit incorporating your program | ||||
| into proprietary programs.  If your program is a subroutine library, you | ||||
| may consider it more useful to permit linking proprietary applications with | ||||
| the library.  If this is what you want to do, use the GNU Lesser General | ||||
| Public License instead of this License.  But first, please read | ||||
| <https://www.gnu.org/licenses/why-not-lgpl.html>. | ||||
							
								
								
									
										124
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,13 +1,121 @@ | ||||
| # CGSpace Statistics API | ||||
| A quick and dirty REST API to expose Solr view and download statistics for items in a DSpace repository. | ||||
| # DSpace Statistics API [](https://ci.mjanja.ch/alanorth/dspace-statistics-api) [](https://builds.sr.ht/~alanorth/dspace-statistics-api?) | ||||
| DSpace stores item view and download events in a Solr "statistics" core. This information is available for use in the various DSpace user interfaces, but is not exposed externally via any APIs. The DSpace 4/5/6 [REST API](https://wiki.lyrasis.org/display/DSDOC5x/REST+API), for example, only exposes information about communities, collections, item metadata, and bitstreams. | ||||
|  | ||||
| Written and tested in Python 3.6. SolrClient (0.2.1) does not currently run in Python 3.7.0. | ||||
| - If your DSpace is version 4 or 5, use [dspace-statistics-api v1.1.1](https://github.com/ilri/dspace-statistics-api/releases/tag/v1.1.1) | ||||
| - If your DSpace is version 6+, use [dspace-statistics-api v1.2.0 or greater](https://github.com/ilri/dspace-statistics-api/releases/tag/v1.2.0) | ||||
|  | ||||
| This project contains an indexer and a [Falcon-based](https://falcon.readthedocs.io/) web application to make the statistics available via a simple REST API. You can read more about the Solr queries used to gather the item view and download statistics on the [DSpace wiki](https://wiki.lyrasis.org/display/DSPACE/Solr). | ||||
|  | ||||
| If you use the DSpace Statistics API please cite: | ||||
|  | ||||
| *Orth, A. 2018. DSpace statistics API. Nairobi, Kenya: ILRI. https://hdl.handle.net/10568/99143.* | ||||
|  | ||||
| ## Requirements | ||||
|  | ||||
| - Python 3.6+ | ||||
| - PostgreSQL version 9.5+ (due to [`UPSERT` support](https://wiki.postgresql.org/wiki/UPSERT)) | ||||
| - DSpace with [Solr usage statistics enabled](https://wiki.lyrasis.org/display/DSDOC5x/SOLR+Statistics) (tested with 5.8+ and 6.3) | ||||
|  | ||||
| ## Installation | ||||
| Create a virtual environment and run it: | ||||
| Create a Python virtual environment and install the dependencies: | ||||
|  | ||||
|     $ virtualenv -p /usr/bin/python3.6 venv | ||||
|     $ . venv/bin/activate | ||||
|     $ pip install falcon gunicorn SolrClient | ||||
|     $ gunicorn app:api | ||||
|     $ python3 -m venv venv | ||||
|     $ source venv/bin/activate | ||||
|     $ pip install -r requirements.txt | ||||
|  | ||||
| ## Running | ||||
|  | ||||
| Set up the environment variables for Solr and PostgreSQL: | ||||
|  | ||||
|     $ export SOLR_SERVER=http://localhost:8080/solr | ||||
|     $ export DATABASE_NAME=dspacestatistics | ||||
|     $ export DATABASE_USER=dspacestatistics | ||||
|     $ export DATABASE_PASS=dspacestatistics | ||||
|     $ export DATABASE_HOST=localhost | ||||
|  | ||||
| Index the Solr statistics core to populate the PostgreSQL database: | ||||
|  | ||||
|     $ python -m dspace_statistics_api.indexer | ||||
|  | ||||
| Run the REST API: | ||||
|  | ||||
|     $ gunicorn dspace_statistics_api.app | ||||
|  | ||||
| Test to see if there are any statistics: | ||||
|  | ||||
|     $ curl 'http://localhost:8000/items?limit=1' | ||||
|  | ||||
| ## Testing | ||||
| Install development packages using pip: | ||||
|  | ||||
|     $ pip install -r requirements-dev.txt | ||||
|  | ||||
| Run tests: | ||||
|  | ||||
|     $ pytest | ||||
|  | ||||
| ## 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. | ||||
|  | ||||
| An example nginx configuration is: | ||||
|  | ||||
| ``` | ||||
| server { | ||||
|     #... | ||||
|  | ||||
|     location ~ /rest/statistics/?(.*) { | ||||
|         access_log /var/log/nginx/statistics.log; | ||||
|         proxy_pass http://statistics_api/$1$is_args$args; | ||||
|     } | ||||
| } | ||||
|  | ||||
| upstream statistics_api { | ||||
|     server 127.0.0.1:5000; | ||||
| } | ||||
| ``` | ||||
|  | ||||
| This would expose the API at `/rest/statistics`. | ||||
|  | ||||
| ## Using the API | ||||
| The API exposes the following endpoints: | ||||
|  | ||||
|   - GET `/` — return a basic API documentation page. | ||||
|   - GET `/items` — return views and downloads for all items that Solr knows about¹. Accepts `limit` and `page` query parameters for pagination of results (`limit` must be an integer between 1 and 100, and `page` must be an integer greater than or equal to 0). | ||||
|   - POST `/items` — return views and downloads for an arbitrary list of items with an optional date range. Accepts `limit`, `page`, `dateFrom`, and `dateTo` parameters². | ||||
|   - GET `/item/id` — return views and downloads for a single item (`id` must be a UUID). Returns HTTP 404 if an item id is not found. | ||||
|  | ||||
| The item id is the *internal* UUID for an item. You can get these from the standard DSpace REST API. | ||||
|  | ||||
| ¹ We are querying the Solr statistics core, which technically only knows about items that have either views or downloads. If an item is not present here you can assume it has zero views and zero downloads, but not necessarily that it does not exist in the repository. | ||||
|  | ||||
| ² POST requests to `/items` should be in JSON format with the following parameters: | ||||
|  | ||||
| ``` | ||||
| { | ||||
|     "limit": 100, // optional, integer between 0 and 100, default 100 | ||||
|     "page": 0, // optional, integer greater than 0, default 0 | ||||
|     "dateFrom": "2020-01-01T00:00:00Z", // optional, default * | ||||
|     "dateTo": "2020-09-09T00:00:00Z", // optional, default * | ||||
|     "items": [ | ||||
|         "f44cf173-2344-4eb2-8f00-ee55df32c76f", | ||||
|         "2324aa41-e9de-4a2b-bc36-16241464683e", | ||||
|         "8542f9da-9ce1-4614-abf4-f2e3fdb4b305", | ||||
|         "0fe573e7-042a-4240-a4d9-753b61233908" | ||||
|     ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## TODO | ||||
|  | ||||
| - Better logging | ||||
| - Version API (or at least include a /version endpoint?) | ||||
| - Use JSON in PostgreSQL | ||||
| - Add top items endpoint, perhaps `/top/items` or `/items/top`? | ||||
|   - Actually we could add `/items?limit=10&sort=views` | ||||
| - Make community and collection stats available | ||||
| - Check IDs in database to see if they are deleted... | ||||
|  | ||||
| ## License | ||||
| This work is licensed under the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html). | ||||
|  | ||||
| The license allows you to use and modify the work for personal and commercial purposes, but if you distribute the work you must provide users with a means to access the source code for the version you are distributing. Read more about the [GPLv3 at TL;DR Legal](https://tldrlegal.com/license/gnu-general-public-license-v3-(gpl-3)). | ||||
|   | ||||
							
								
								
									
										44
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										44
									
								
								app.py
									
									
									
									
									
								
							| @@ -1,44 +0,0 @@ | ||||
| # Tested with Python 3.6 | ||||
| # See DSpace Solr docs for tips about parameters | ||||
| # https://wiki.duraspace.org/display/DSPACE/Solr | ||||
|  | ||||
| import falcon | ||||
| from SolrClient import SolrClient | ||||
|  | ||||
| solr_server = 'http://localhost:3000/solr' | ||||
| solr_core = 'statistics' | ||||
|  | ||||
| class ItemResource: | ||||
|     def on_get(self, req, resp): | ||||
|         """Handles GET requests""" | ||||
|         # Return HTTPBadRequest if id parameter is not present and valid | ||||
|         item_id = req.get_param_as_int("id", required=True, min=0) | ||||
|  | ||||
|         solr = SolrClient(solr_server) | ||||
|  | ||||
|         # Get views | ||||
|         res = solr.query(solr_core, { | ||||
|             'q':'type:0', | ||||
|             'fq':'owningItem:{0} AND isBot:false AND statistics_type:view AND -bundleName:ORIGINAL'.format(item_id) | ||||
|         }) | ||||
|  | ||||
|         views = res.get_num_found() | ||||
|  | ||||
|         # Get downloads | ||||
|         res = solr.query(solr_core, { | ||||
|             'q':'type:0', | ||||
|             'fq':'owningItem:{0} AND isBot:false AND statistics_type:view AND -(bundleName:[* TO *] -bundleName:ORIGINAL)'.format(item_id) | ||||
|         }) | ||||
|  | ||||
|         downloads = res.get_num_found()  | ||||
|  | ||||
|         statistics = { | ||||
|             'id': item_id, | ||||
|             'views': views, | ||||
|             'downloads': downloads | ||||
|         } | ||||
|  | ||||
|         resp.media = statistics | ||||
|  | ||||
| api = falcon.API() | ||||
| api.add_route('/item', ItemResource()) | ||||
							
								
								
									
										20
									
								
								contrib/dspace-statistics-api.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								contrib/dspace-statistics-api.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| [Unit] | ||||
| Description=DSpace Statistics API | ||||
| After=network.target | ||||
|  | ||||
| [Service] | ||||
| Environment=DATABASE_NAME=dspacestatistics | ||||
| Environment=DATABASE_USER=dspacestatistics | ||||
| Environment=DATABASE_PASS=dspacestatistics | ||||
| Environment=DATABASE_HOST=localhost | ||||
| User=nobody | ||||
| Group=nogroup | ||||
| WorkingDirectory=/var/lib/dspace-statistics-api | ||||
| ExecStart=/var/lib/dspace-statistics-api/venv/bin/gunicorn \ | ||||
|           --bind 127.0.0.1:5000                             \ | ||||
|           dspace_statistics_api.app | ||||
| ExecReload=/bin/kill -s HUP $MAINPID | ||||
| ExecStop=/bin/kill -s TERM $MAINPID | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										17
									
								
								contrib/dspace-statistics-indexer.service
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								contrib/dspace-statistics-indexer.service
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| [Unit] | ||||
| Description=DSpace Statistics Indexer | ||||
| After=tomcat7.target | ||||
|  | ||||
| [Service] | ||||
| Environment=SOLR_SERVER=http://localhost:8081/solr | ||||
| Environment=DATABASE_NAME=dspacestatistics | ||||
| Environment=DATABASE_USER=dspacestatistics | ||||
| Environment=DATABASE_PASS=dspacestatistics | ||||
| Environment=DATABASE_HOST=localhost | ||||
| User=nobody | ||||
| Group=nogroup | ||||
| WorkingDirectory=/var/lib/dspace-statistics-api | ||||
| ExecStart=/var/lib/dspace-statistics-api/venv/bin/python -m dspace_statistics_api.indexer | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
							
								
								
									
										12
									
								
								contrib/dspace-statistics-indexer.timer
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								contrib/dspace-statistics-indexer.timer
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| [Unit] | ||||
| Description=DSpace Statistics Indexer | ||||
|  | ||||
| [Timer] | ||||
| # twice a day, at 6AM and 6PM | ||||
| OnCalendar=*-*-* 06,18:00:00 | ||||
| # Add a random delay of 0–3600 seconds | ||||
| RandomizedDelaySec=3600 | ||||
| Persistent=true | ||||
|  | ||||
| [Install] | ||||
| WantedBy=timers.target | ||||
							
								
								
									
										0
									
								
								dspace_statistics_api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								dspace_statistics_api/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										159
									
								
								dspace_statistics_api/app.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								dspace_statistics_api/app.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| import falcon | ||||
|  | ||||
| from .database import DatabaseManager | ||||
| from .items import get_downloads, get_views | ||||
| from .util import validate_items_post_parameters | ||||
|  | ||||
|  | ||||
| class RootResource: | ||||
|     def on_get(self, req, resp): | ||||
|         resp.status = falcon.HTTP_200 | ||||
|         resp.content_type = "text/html" | ||||
|         with open("dspace_statistics_api/docs/index.html", "r") as f: | ||||
|             resp.body = f.read() | ||||
|  | ||||
|  | ||||
| class AllItemsResource: | ||||
|     def on_get(self, req, resp): | ||||
|         """Handles GET requests""" | ||||
|         # Return HTTPBadRequest if id parameter is not present and valid | ||||
|         limit = req.get_param_as_int("limit", min_value=1, max_value=100) or 100 | ||||
|         page = req.get_param_as_int("page", min_value=0) or 0 | ||||
|         offset = limit * page | ||||
|  | ||||
|         with DatabaseManager() as db: | ||||
|             db.set_session(readonly=True) | ||||
|  | ||||
|             with db.cursor() as cursor: | ||||
|                 # get total number of items so we can estimate the pages | ||||
|                 cursor.execute("SELECT COUNT(id) FROM items") | ||||
|                 pages = round(cursor.fetchone()[0] / limit) | ||||
|  | ||||
|                 # get statistics and use limit and offset to page through results | ||||
|                 cursor.execute( | ||||
|                     "SELECT id, views, downloads FROM items ORDER BY id LIMIT %s OFFSET %s", | ||||
|                     [limit, offset], | ||||
|                 ) | ||||
|  | ||||
|                 # create a list to hold dicts of item stats | ||||
|                 statistics = list() | ||||
|  | ||||
|                 # iterate over results and build statistics object | ||||
|                 for item in cursor: | ||||
|                     statistics.append( | ||||
|                         { | ||||
|                             "id": str(item["id"]), | ||||
|                             "views": item["views"], | ||||
|                             "downloads": item["downloads"], | ||||
|                         } | ||||
|                     ) | ||||
|  | ||||
|         message = { | ||||
|             "currentPage": page, | ||||
|             "totalPages": pages, | ||||
|             "limit": limit, | ||||
|             "statistics": statistics, | ||||
|         } | ||||
|  | ||||
|         resp.media = message | ||||
|  | ||||
|     @falcon.before(validate_items_post_parameters) | ||||
|     def on_post(self, req, resp): | ||||
|         """Handles POST requests""" | ||||
|  | ||||
|         # Build the Solr date string, ie: [* TO *] | ||||
|         if req.context.dateFrom and req.context.dateTo: | ||||
|             solr_date_string = f"[{req.context.dateFrom} TO {req.context.dateTo}]" | ||||
|         elif not req.context.dateFrom and req.context.dateTo: | ||||
|             solr_date_string = f"[* TO {req.context.dateTo}]" | ||||
|         elif req.context.dateFrom and not req.context.dateTo: | ||||
|             solr_date_string = f"[{req.context.dateFrom} TO *]" | ||||
|         else: | ||||
|             solr_date_string = "[* TO *]" | ||||
|  | ||||
|         # Helper variables to make working with pages/items/results easier and | ||||
|         # to make the code easier to understand | ||||
|         number_of_items: int = len(req.context.items) | ||||
|         pages: int = int(number_of_items / req.context.limit) | ||||
|         first_item: int = req.context.page * req.context.limit | ||||
|         last_item: int = first_item + req.context.limit | ||||
|         # Get a subset of the POSTed items based on our limit. Note that Python | ||||
|         # list slicing and indexing are both zero based, but the first and last | ||||
|         # items in a slice can be confusing. See this ASCII diagram: | ||||
|         # | ||||
|         #                 +---+---+---+---+---+---+ | ||||
|         #                 | P | y | t | h | o | n | | ||||
|         #                 +---+---+---+---+---+---+ | ||||
|         # Slice position: 0   1   2   3   4   5   6 | ||||
|         # Index position:   0   1   2   3   4   5 | ||||
|         # | ||||
|         # So if we have a list items with 240 items: | ||||
|         # | ||||
|         #   1st set: items[0:100] would give items at indexes 0 to 99 | ||||
|         #   2nd set: items[100:200] would give items at indexes 100 to 199 | ||||
|         #   3rd set: items[200:300] would give items at indexes 200 to 239 | ||||
|         items_subset: list = req.context.items[first_item:last_item] | ||||
|  | ||||
|         views: dict = get_views(solr_date_string, items_subset) | ||||
|         downloads: dict = get_downloads(solr_date_string, items_subset) | ||||
|  | ||||
|         # create a list to hold dicts of item stats | ||||
|         statistics = list() | ||||
|  | ||||
|         # iterate over views dict to extract views and use the item id as an | ||||
|         # index to the downloads dict to extract downloads. | ||||
|         for k, v in views.items(): | ||||
|             statistics.append({"id": k, "views": v, "downloads": downloads[k]}) | ||||
|  | ||||
|         message = { | ||||
|             "currentPage": req.context.page, | ||||
|             "totalPages": pages, | ||||
|             "limit": req.context.limit, | ||||
|             "statistics": statistics, | ||||
|         } | ||||
|  | ||||
|         resp.status = falcon.HTTP_200 | ||||
|         resp.media = message | ||||
|  | ||||
|  | ||||
| class ItemResource: | ||||
|     def on_get(self, req, resp, item_id): | ||||
|         """Handles GET requests""" | ||||
|  | ||||
|         import psycopg2.extras | ||||
|  | ||||
|         # Adapt Python’s uuid.UUID type to PostgreSQL’s uuid | ||||
|         # See: https://www.psycopg.org/docs/extras.html | ||||
|         psycopg2.extras.register_uuid() | ||||
|  | ||||
|         with DatabaseManager() as db: | ||||
|             db.set_session(readonly=True) | ||||
|  | ||||
|             with db.cursor() as cursor: | ||||
|                 cursor = db.cursor() | ||||
|                 cursor.execute( | ||||
|                     "SELECT views, downloads FROM items WHERE id=%s", [str(item_id)] | ||||
|                 ) | ||||
|                 if cursor.rowcount == 0: | ||||
|                     raise falcon.HTTPNotFound( | ||||
|                         title="Item not found", | ||||
|                         description=f'The item with id "{str(item_id)}" was not found.', | ||||
|                     ) | ||||
|                 else: | ||||
|                     results = cursor.fetchone() | ||||
|  | ||||
|                     statistics = { | ||||
|                         "id": str(item_id), | ||||
|                         "views": results["views"], | ||||
|                         "downloads": results["downloads"], | ||||
|                     } | ||||
|  | ||||
|                     resp.media = statistics | ||||
|  | ||||
|  | ||||
| api = application = falcon.API() | ||||
| api.add_route("/", RootResource()) | ||||
| api.add_route("/items", AllItemsResource()) | ||||
| api.add_route("/item/{item_id:uuid}", ItemResource()) | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										12
									
								
								dspace_statistics_api/config.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								dspace_statistics_api/config.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| import os | ||||
|  | ||||
| # Check if Solr connection information was provided in the environment | ||||
| SOLR_SERVER = os.environ.get("SOLR_SERVER", "http://localhost:8080/solr") | ||||
|  | ||||
| DATABASE_NAME = os.environ.get("DATABASE_NAME", "dspacestatistics") | ||||
| DATABASE_USER = os.environ.get("DATABASE_USER", "dspacestatistics") | ||||
| DATABASE_PASS = os.environ.get("DATABASE_PASS", "dspacestatistics") | ||||
| DATABASE_HOST = os.environ.get("DATABASE_HOST", "localhost") | ||||
| DATABASE_PORT = os.environ.get("DATABASE_PORT", "5432") | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										36
									
								
								dspace_statistics_api/database.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								dspace_statistics_api/database.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| import falcon | ||||
| import psycopg2 | ||||
| import psycopg2.extras | ||||
|  | ||||
| from .config import ( | ||||
|     DATABASE_HOST, | ||||
|     DATABASE_NAME, | ||||
|     DATABASE_PASS, | ||||
|     DATABASE_PORT, | ||||
|     DATABASE_USER, | ||||
| ) | ||||
|  | ||||
|  | ||||
| class DatabaseManager: | ||||
|     """Manage database connection.""" | ||||
|  | ||||
|     def __init__(self): | ||||
|         self._connection_uri = f"dbname={DATABASE_NAME} user={DATABASE_USER} password={DATABASE_PASS} host={DATABASE_HOST} port={DATABASE_PORT}" | ||||
|  | ||||
|     def __enter__(self): | ||||
|         try: | ||||
|             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 | ||||
|  | ||||
|     def __exit__(self, exc_type, exc_value, exc_traceback): | ||||
|         self._connection.close() | ||||
|  | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										37
									
								
								dspace_statistics_api/docs/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								dspace_statistics_api/docs/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en-US"> | ||||
|     <head> | ||||
|         <meta charset="UTF-8"> | ||||
|         <title>DSpace Statistics API</title> | ||||
|     </head> | ||||
|     <body> | ||||
|         <h1>DSpace Statistics API v1.3.2</h1> | ||||
|         <p>This site is running the <a href="https://github.com/ilri/dspace-statistics-api" title="DSpace Statistics API project">DSpace Statistics API</a>. The following endpoints are available:</p> | ||||
|         <ul> | ||||
|             <li>GET <code>/</code> — return a basic API documentation page.</li> | ||||
|             <li>GET <code>/items</code> — return views and downloads for all items that Solr knows about¹. Accepts <code>limit</code> and <code>page</code> query parameters for pagination of results (<code>limit</code> must be an integer between 1 and 100, and <code>page</code> must be an integer greater than or equal to 0).</li> | ||||
|             <li>POST <code>/items</code> — return views and downloads for an arbitrary list of items with an optional date range. Accepts <code>limit</code>, <code>page</code>, <code>dateFrom</code>, and <code>dateTo</code> parameters².</li> | ||||
|             <li>GET <code>/item/id</code> — return views and downloads for a single item (<code>id</code> must be a UUID). Returns HTTP 404 if an item id is not found.</li> | ||||
|         </ul> | ||||
|  | ||||
|         <p>The item id is the <em>internal</em> uuid for an item. You can get these from the standard DSpace REST API.</p> | ||||
|  | ||||
|         <hr/> | ||||
|  | ||||
|         <p>¹ We are querying the Solr statistics core, which technically only knows about items that have either views or downloads. If an item is not present here you can assume it has zero views and zero downloads, but not necessarily that it does not exist in the repository.</p> | ||||
|         <p>² POST requests to <code>/items</code> should be in JSON format with the following parameters: | ||||
| 			<pre><code>{ | ||||
| 	"limit": 100, // optional, integer between 1 and 100, default 100 | ||||
| 	"page": 0, // optional, integer greater than 0, default 0 | ||||
| 	"dateFrom": "2020-01-01T00:00:00Z", // optional, default * | ||||
| 	"dateTo": "2020-09-09T00:00:00Z", // optional, default * | ||||
| 	"items": [ | ||||
| 		"f44cf173-2344-4eb2-8f00-ee55df32c76f", | ||||
| 		"2324aa41-e9de-4a2b-bc36-16241464683e", | ||||
| 		"8542f9da-9ce1-4614-abf4-f2e3fdb4b305", | ||||
| 		"0fe573e7-042a-4240-a4d9-753b61233908" | ||||
| 	] | ||||
| }</code></pre> | ||||
|         </p> | ||||
|     </body> | ||||
| </html> | ||||
							
								
								
									
										223
									
								
								dspace_statistics_api/indexer.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										223
									
								
								dspace_statistics_api/indexer.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,223 @@ | ||||
| # | ||||
| # indexer.py | ||||
| # | ||||
| # Copyright 2018 Alan Orth. | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
| # | ||||
| # --- | ||||
| # | ||||
| # Connects to a DSpace Solr statistics core and ingests item views and downloads | ||||
| # into a PostgreSQL database for use by other applications (like an API). | ||||
| # | ||||
| # This script is written for Python 3.6+ and requires several modules that you | ||||
| # can install with pip (I recommend using a Python virtual environment): | ||||
| # | ||||
| #   $ pip install psycopg2-binary | ||||
| # | ||||
| # See: https://wiki.duraspace.org/display/DSPACE/Solr | ||||
|  | ||||
| import psycopg2.extras | ||||
| import requests | ||||
|  | ||||
| from .config import SOLR_SERVER | ||||
| from .database import DatabaseManager | ||||
| from .util import get_statistics_shards | ||||
|  | ||||
|  | ||||
| def index_views(): | ||||
|     # get total number of distinct facets for items with a minimum of 1 view, | ||||
|     # otherwise Solr returns all kinds of weird ids that are actually not in | ||||
|     # the database. Also, stats are expensive, but we need stats.calcdistinct | ||||
|     # so we can get the countDistinct summary to calculate how many pages of | ||||
|     # results we have. | ||||
|     # | ||||
|     # see: https://lucene.apache.org/solr/guide/6_6/the-stats-component.html | ||||
|     solr_query_params = { | ||||
|         "q": "type:2", | ||||
|         "fq": "-isBot:true AND statistics_type:view", | ||||
|         "facet": "true", | ||||
|         "facet.field": "id", | ||||
|         "facet.mincount": 1, | ||||
|         "facet.limit": 1, | ||||
|         "facet.offset": 0, | ||||
|         "stats": "true", | ||||
|         "stats.field": "id", | ||||
|         "stats.calcdistinct": "true", | ||||
|         "shards": shards, | ||||
|         "rows": 0, | ||||
|         "wt": "json", | ||||
|     } | ||||
|  | ||||
|     solr_url = SOLR_SERVER + "/statistics/select" | ||||
|  | ||||
|     res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|     try: | ||||
|         # get total number of distinct facets (countDistinct) | ||||
|         results_totalNumFacets = res.json()["stats"]["stats_fields"]["id"][ | ||||
|             "countDistinct" | ||||
|         ] | ||||
|     except TypeError: | ||||
|         print("No item views to index, exiting.") | ||||
|  | ||||
|         exit(0) | ||||
|  | ||||
|     # divide results into "pages" (cast to int to effectively round down) | ||||
|     results_per_page = 100 | ||||
|     results_num_pages = int(results_totalNumFacets / results_per_page) | ||||
|     results_current_page = 0 | ||||
|  | ||||
|     with DatabaseManager() as db: | ||||
|         with db.cursor() as cursor: | ||||
|             # create an empty list to store values for batch insertion | ||||
|             data = [] | ||||
|  | ||||
|             while results_current_page <= results_num_pages: | ||||
|                 # "pages" are zero based, but one based is more human readable | ||||
|                 print( | ||||
|                     f"Indexing item views (page {results_current_page + 1} of {results_num_pages + 1})" | ||||
|                 ) | ||||
|  | ||||
|                 solr_query_params = { | ||||
|                     "q": "type:2", | ||||
|                     "fq": "-isBot:true AND statistics_type:view", | ||||
|                     "facet": "true", | ||||
|                     "facet.field": "id", | ||||
|                     "facet.mincount": 1, | ||||
|                     "facet.limit": results_per_page, | ||||
|                     "facet.offset": results_current_page * results_per_page, | ||||
|                     "shards": shards, | ||||
|                     "rows": 0, | ||||
|                     "wt": "json", | ||||
|                     "json.nl": "map",  # return facets as a dict instead of a flat list | ||||
|                 } | ||||
|  | ||||
|                 res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|                 # Solr returns facets as a dict of dicts (see json.nl parameter) | ||||
|                 views = res.json()["facet_counts"]["facet_fields"] | ||||
|                 # iterate over the 'id' dict and get the item ids and views | ||||
|                 for item_id, item_views in views["id"].items(): | ||||
|                     data.append((item_id, item_views)) | ||||
|  | ||||
|                 # do a batch insert of values from the current "page" of results | ||||
|                 sql = "INSERT INTO items(id, views) VALUES %s ON CONFLICT(id) DO UPDATE SET views=excluded.views" | ||||
|                 psycopg2.extras.execute_values(cursor, sql, data, template="(%s, %s)") | ||||
|                 db.commit() | ||||
|  | ||||
|                 # clear all items from the list so we can populate it with the next batch | ||||
|                 data.clear() | ||||
|  | ||||
|                 results_current_page += 1 | ||||
|  | ||||
|  | ||||
| def index_downloads(): | ||||
|     # get the total number of distinct facets for items with at least 1 download | ||||
|     solr_query_params = { | ||||
|         "q": "type:0", | ||||
|         "fq": "-isBot:true AND statistics_type:view AND bundleName:ORIGINAL", | ||||
|         "facet": "true", | ||||
|         "facet.field": "owningItem", | ||||
|         "facet.mincount": 1, | ||||
|         "facet.limit": 1, | ||||
|         "facet.offset": 0, | ||||
|         "stats": "true", | ||||
|         "stats.field": "owningItem", | ||||
|         "stats.calcdistinct": "true", | ||||
|         "shards": shards, | ||||
|         "rows": 0, | ||||
|         "wt": "json", | ||||
|     } | ||||
|  | ||||
|     solr_url = SOLR_SERVER + "/statistics/select" | ||||
|  | ||||
|     res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|     try: | ||||
|         # get total number of distinct facets (countDistinct) | ||||
|         results_totalNumFacets = res.json()["stats"]["stats_fields"]["owningItem"][ | ||||
|             "countDistinct" | ||||
|         ] | ||||
|     except TypeError: | ||||
|         print("No item downloads to index, exiting.") | ||||
|  | ||||
|         exit(0) | ||||
|  | ||||
|     # divide results into "pages" (cast to int to effectively round down) | ||||
|     results_per_page = 100 | ||||
|     results_num_pages = int(results_totalNumFacets / results_per_page) | ||||
|     results_current_page = 0 | ||||
|  | ||||
|     with DatabaseManager() as db: | ||||
|         with db.cursor() as cursor: | ||||
|             # create an empty list to store values for batch insertion | ||||
|             data = [] | ||||
|  | ||||
|             while results_current_page <= results_num_pages: | ||||
|                 # "pages" are zero based, but one based is more human readable | ||||
|                 print( | ||||
|                     f"Indexing item downloads (page {results_current_page + 1} of {results_num_pages + 1})" | ||||
|                 ) | ||||
|  | ||||
|                 solr_query_params = { | ||||
|                     "q": "type:0", | ||||
|                     "fq": "-isBot:true AND statistics_type:view AND bundleName:ORIGINAL", | ||||
|                     "facet": "true", | ||||
|                     "facet.field": "owningItem", | ||||
|                     "facet.mincount": 1, | ||||
|                     "facet.limit": results_per_page, | ||||
|                     "facet.offset": results_current_page * results_per_page, | ||||
|                     "shards": shards, | ||||
|                     "rows": 0, | ||||
|                     "wt": "json", | ||||
|                     "json.nl": "map",  # return facets as a dict instead of a flat list | ||||
|                 } | ||||
|  | ||||
|                 res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|                 # Solr returns facets as a dict of dicts (see json.nl parameter) | ||||
|                 downloads = res.json()["facet_counts"]["facet_fields"] | ||||
|                 # iterate over the 'owningItem' dict and get the item ids and downloads | ||||
|                 for item_id, item_downloads in downloads["owningItem"].items(): | ||||
|                     data.append((item_id, item_downloads)) | ||||
|  | ||||
|                 # do a batch insert of values from the current "page" of results | ||||
|                 sql = "INSERT INTO items(id, downloads) VALUES %s ON CONFLICT(id) DO UPDATE SET downloads=excluded.downloads" | ||||
|                 psycopg2.extras.execute_values(cursor, sql, data, template="(%s, %s)") | ||||
|                 db.commit() | ||||
|  | ||||
|                 # clear all items from the list so we can populate it with the next batch | ||||
|                 data.clear() | ||||
|  | ||||
|                 results_current_page += 1 | ||||
|  | ||||
|  | ||||
| with DatabaseManager() as db: | ||||
|     with db.cursor() as cursor: | ||||
|         # create table to store item views and downloads | ||||
|         cursor.execute( | ||||
|             """CREATE TABLE IF NOT EXISTS items | ||||
|                   (id UUID PRIMARY KEY, views INT DEFAULT 0, downloads INT DEFAULT 0)""" | ||||
|         ) | ||||
|  | ||||
|     # commit the table creation before closing the database connection | ||||
|     db.commit() | ||||
|  | ||||
| shards = get_statistics_shards() | ||||
|  | ||||
| index_views() | ||||
| index_downloads() | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										105
									
								
								dspace_statistics_api/items.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								dspace_statistics_api/items.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| import requests | ||||
|  | ||||
| from .config import SOLR_SERVER | ||||
|  | ||||
|  | ||||
| def get_views(solr_date_string: str, items: list): | ||||
|     """ | ||||
|     Get view statistics for a list of items from Solr. | ||||
|  | ||||
|     :parameter solr_date_string (str): Solr date string, for example "[* TO *]" | ||||
|     :parameter items (list): a list of item IDs | ||||
|     :returns: A dict of item IDs and views | ||||
|     """ | ||||
|     from .util import get_statistics_shards | ||||
|     shards = get_statistics_shards() | ||||
|  | ||||
|     # Join the UUIDs with "OR" and escape the hyphens for Solr | ||||
|     solr_items_string: str = " OR ".join(items).replace("-", r"\-") | ||||
|  | ||||
|     solr_query_params = { | ||||
|         "q": f"id:({solr_items_string})", | ||||
|         "fq": f"type:2 AND isBot:false AND statistics_type:view AND time:{solr_date_string}", | ||||
|         "facet": "true", | ||||
|         "facet.field": "id", | ||||
|         "facet.mincount": 1, | ||||
|         "shards": shards, | ||||
|         "rows": 0, | ||||
|         "wt": "json", | ||||
|         "json.nl": "map",  # return facets as a dict instead of a flat list | ||||
|     } | ||||
|  | ||||
|     solr_url = SOLR_SERVER + "/statistics/select" | ||||
|     res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|     # Create an empty dict to store views | ||||
|     data = {} | ||||
|  | ||||
|     # Solr returns facets as a dict of dicts (see the json.nl parameter) | ||||
|     views = res.json()["facet_counts"]["facet_fields"] | ||||
|     # iterate over the 'id' dict and get the item ids and views | ||||
|     for item_id, item_views in views["id"].items(): | ||||
|         data[item_id] = item_views | ||||
|  | ||||
|     # Check if any items have missing stats so we can set them to 0 | ||||
|     if len(data) < len(items): | ||||
|         # List comprehension to get a list of item ids (keys) in the data | ||||
|         data_ids = [k for k, v in data.items()] | ||||
|         for item_id in items: | ||||
|             if item_id not in data_ids: | ||||
|                 data[item_id] = 0 | ||||
|                 continue | ||||
|  | ||||
|     return data | ||||
|  | ||||
|  | ||||
| def get_downloads(solr_date_string: str, items: list): | ||||
|     """ | ||||
|     Get download statistics for a list of items from Solr. | ||||
|  | ||||
|     :parameter solr_date_string (str): Solr date string, for example "[* TO *]" | ||||
|     :parameter items (list): a list of item IDs | ||||
|     :returns: A dict of item IDs and downloads | ||||
|     """ | ||||
|     from .util import get_statistics_shards | ||||
|     shards = get_statistics_shards() | ||||
|  | ||||
|     # Join the UUIDs with "OR" and escape the hyphens for Solr | ||||
|     solr_items_string: str = " OR ".join(items).replace("-", r"\-") | ||||
|  | ||||
|     solr_query_params = { | ||||
|         "q": f"owningItem:({solr_items_string})", | ||||
|         "fq": f"type:0 AND isBot:false AND statistics_type:view AND bundleName:ORIGINAL AND time:{solr_date_string}", | ||||
|         "facet": "true", | ||||
|         "facet.field": "owningItem", | ||||
|         "facet.mincount": 1, | ||||
|         "shards": shards, | ||||
|         "rows": 0, | ||||
|         "wt": "json", | ||||
|         "json.nl": "map",  # return facets as a dict instead of a flat list | ||||
|     } | ||||
|  | ||||
|     solr_url = SOLR_SERVER + "/statistics/select" | ||||
|     res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|     # Create an empty dict to store downloads | ||||
|     data = {} | ||||
|  | ||||
|     # Solr returns facets as a dict of dicts (see the json.nl parameter) | ||||
|     downloads = res.json()["facet_counts"]["facet_fields"] | ||||
|     # Iterate over the 'owningItem' dict and get the item ids and downloads | ||||
|     for item_id, item_downloads in downloads["owningItem"].items(): | ||||
|         data[item_id] = item_downloads | ||||
|  | ||||
|     # Check if any items have missing stats so we can set them to 0 | ||||
|     if len(data) < len(items): | ||||
|         # List comprehension to get a list of item ids (keys) in the data | ||||
|         data_ids = [k for k, v in data.items()] | ||||
|         for item_id in items: | ||||
|             if item_id not in data_ids: | ||||
|                 data[item_id] = 0 | ||||
|                 continue | ||||
|  | ||||
|     return data | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										138
									
								
								dspace_statistics_api/util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								dspace_statistics_api/util.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,138 @@ | ||||
| import falcon | ||||
|  | ||||
|  | ||||
| def get_statistics_shards(): | ||||
|     """Enumerate the cores in Solr to determine if statistics have been sharded into | ||||
|     yearly shards by DSpace's stats-util or not (for example: statistics-2018). | ||||
|  | ||||
|     Returns: | ||||
|         str:A list of Solr statistics shards separated by commas. | ||||
|     """ | ||||
|     import re | ||||
|  | ||||
|     import requests | ||||
|  | ||||
|     from .config import SOLR_SERVER | ||||
|  | ||||
|     # Initialize an empty list for statistics core years | ||||
|     statistics_core_years = [] | ||||
|  | ||||
|     # URL for Solr status to check active cores | ||||
|     solr_query_params = {"action": "STATUS", "wt": "json"} | ||||
|     solr_url = SOLR_SERVER + "/admin/cores" | ||||
|     res = requests.get(solr_url, params=solr_query_params) | ||||
|  | ||||
|     if res.status_code == requests.codes.ok: | ||||
|         data = res.json() | ||||
|  | ||||
|         # Iterate over active cores from Solr's STATUS response (cores are in | ||||
|         # the status array of this response). | ||||
|         for core in data["status"]: | ||||
|             # Pattern to match, for example: statistics-2018 | ||||
|             pattern = re.compile("^statistics-[0-9]{4}$") | ||||
|  | ||||
|             if not pattern.match(core): | ||||
|                 continue | ||||
|  | ||||
|             # Append current core to list | ||||
|             statistics_core_years.append(core) | ||||
|  | ||||
|     # Initialize a string to hold our shards (may end up being empty if the Solr | ||||
|     # core has not been processed by stats-util). | ||||
|     shards = str() | ||||
|  | ||||
|     if len(statistics_core_years) > 0: | ||||
|         # Begin building a string of shards starting with the default one | ||||
|         shards = f"{SOLR_SERVER}/statistics" | ||||
|  | ||||
|         for core in statistics_core_years: | ||||
|             # Create a comma-separated list of shards to pass to our Solr query | ||||
|             # | ||||
|             # See: https://wiki.apache.org/solr/DistributedSearch | ||||
|             shards += f",{SOLR_SERVER}/{core}" | ||||
|  | ||||
|     # 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 | ||||
|     # any negative performance impact so this should be fine. | ||||
|     return shards | ||||
|  | ||||
|  | ||||
| def is_valid_date(date): | ||||
|     import datetime | ||||
|  | ||||
|     try: | ||||
|         # Solr date format is: 2020-01-01T00:00:00Z | ||||
|         # See: https://docs.python.org/3/library/datetime.html#strftime-strptime-behavior | ||||
|         datetime.datetime.strptime(date, "%Y-%m-%dT%H:%M:%SZ") | ||||
|  | ||||
|         return True | ||||
|     except ValueError: | ||||
|         raise falcon.HTTPBadRequest( | ||||
|             title="Invalid parameter", | ||||
|             description=f"Invalid date format: {date}. The value must be in format: 2020-01-01T00:00:00Z.", | ||||
|         ) | ||||
|  | ||||
|  | ||||
| def validate_items_post_parameters(req, resp, resource, params): | ||||
|     """Check the POSTed request parameters for the `/items` endpoint. | ||||
|  | ||||
|     Meant to be used as a `before` hook. | ||||
|     """ | ||||
|     import json | ||||
|  | ||||
|     # Only attempt to read the POSTed request if its length is not 0 (or | ||||
|     # rather, in the Python sense, if length is not a False-y value). | ||||
|     if req.content_length: | ||||
|         doc = json.load(req.bounded_stream) | ||||
|     else: | ||||
|         raise falcon.HTTPBadRequest( | ||||
|             title="Invalid request", description="Request body is empty." | ||||
|         ) | ||||
|  | ||||
|     # Parse date parameters from request body (will raise an HTTPBadRequest | ||||
|     # from is_valid_date() if any parameters are invalid) | ||||
|     if "dateFrom" in doc and is_valid_date(doc["dateFrom"]): | ||||
|         req.context.dateFrom = doc["dateFrom"] | ||||
|     else: | ||||
|         req.context.dateFrom = None | ||||
|  | ||||
|     if "dateTo" in doc and is_valid_date(doc["dateTo"]): | ||||
|         req.context.dateTo = doc["dateTo"] | ||||
|     else: | ||||
|         req.context.dateTo = None | ||||
|  | ||||
|     # Parse the limit parameter from the POST request body | ||||
|     if "limit" in doc: | ||||
|         if isinstance(doc["limit"], int) and 0 < doc["limit"] <= 100: | ||||
|             req.context.limit = doc["limit"] | ||||
|         else: | ||||
|             raise falcon.HTTPBadRequest( | ||||
|                 title="Invalid parameter", | ||||
|                 description='The "limit" parameter is invalid. The value must be an integer between 1 and 100.', | ||||
|             ) | ||||
|     else: | ||||
|         req.context.limit = 100 | ||||
|  | ||||
|     # Parse the page parameter from the POST request body | ||||
|     if "page" in doc: | ||||
|         if isinstance(doc["page"], int) and doc["page"] >= 0: | ||||
|             req.context.page = doc["page"] | ||||
|         else: | ||||
|             raise falcon.HTTPBadRequest( | ||||
|                 title="Invalid parameter", | ||||
|                 description='The "page" parameter is invalid. The value must be at least 0.', | ||||
|             ) | ||||
|     else: | ||||
|         req.context.page = 0 | ||||
|  | ||||
|     # Parse the list of items from the POST request body | ||||
|     if "items" in doc: | ||||
|         if isinstance(doc["items"], list) and len(doc["items"]) > 0: | ||||
|             req.context.items = doc["items"] | ||||
|         else: | ||||
|             raise falcon.HTTPBadRequest( | ||||
|                 title="Invalid parameter", | ||||
|                 description='The "items" parameter is invalid. The value must be a comma-separated list of item UUIDs.', | ||||
|             ) | ||||
|     else: | ||||
|         req.context.items = list() | ||||
							
								
								
									
										852
									
								
								poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										852
									
								
								poetry.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,852 @@ | ||||
| [[package]] | ||||
| name = "appdirs" | ||||
| version = "1.4.4" | ||||
| description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "appnope" | ||||
| version = "0.1.2" | ||||
| description = "Disable App Nap on macOS >= 10.9" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"darwin\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "atomicwrites" | ||||
| version = "1.4.0" | ||||
| description = "Atomic file writes." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| marker = "sys_platform == \"win32\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "attrs" | ||||
| version = "20.3.0" | ||||
| description = "Classes Without Boilerplate" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [package.extras] | ||||
| dev = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] | ||||
| docs = ["furo", "sphinx", "zope.interface"] | ||||
| tests = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] | ||||
| tests_no_zope = ["coverage (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] | ||||
|  | ||||
| [[package]] | ||||
| name = "backcall" | ||||
| version = "0.2.0" | ||||
| description = "Specifications for callback functions passed in to an API" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "black" | ||||
| version = "20.8b1" | ||||
| description = "The uncompromising code formatter." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
|  | ||||
| [package.extras] | ||||
| colorama = ["colorama (>=0.4.3)"] | ||||
| d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] | ||||
|  | ||||
| [package.dependencies] | ||||
| appdirs = "*" | ||||
| click = ">=7.1.2" | ||||
| mypy-extensions = ">=0.4.3" | ||||
| pathspec = ">=0.6,<1" | ||||
| regex = ">=2020.1.8" | ||||
| toml = ">=0.10.1" | ||||
| typed-ast = ">=1.4.0" | ||||
| typing-extensions = ">=3.7.4" | ||||
|  | ||||
| [package.dependencies.dataclasses] | ||||
| version = ">=0.6" | ||||
| python = "<3.7" | ||||
|  | ||||
| [[package]] | ||||
| name = "certifi" | ||||
| version = "2020.12.5" | ||||
| description = "Python package for providing Mozilla's CA Bundle." | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "chardet" | ||||
| version = "3.0.4" | ||||
| description = "Universal encoding detector for Python 2 and 3" | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "click" | ||||
| version = "7.1.2" | ||||
| description = "Composable command line interface toolkit" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "colorama" | ||||
| version = "0.4.4" | ||||
| description = "Cross-platform colored terminal text." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\" or sys_platform == \"win32\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "dataclasses" | ||||
| version = "0.6" | ||||
| description = "A backport of the dataclasses module for Python 3.6" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version < \"3.7\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "decorator" | ||||
| version = "4.4.2" | ||||
| description = "Decorators for Humans" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.6, !=3.0.*, !=3.1.*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "falcon" | ||||
| version = "2.0.0" | ||||
| description = "An unladen web framework for building APIs and app backends." | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "flake8" | ||||
| version = "3.8.4" | ||||
| description = "the modular source code checker: pep8 pyflakes and co" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" | ||||
|  | ||||
| [package.dependencies] | ||||
| mccabe = ">=0.6.0,<0.7.0" | ||||
| pycodestyle = ">=2.6.0a1,<2.7.0" | ||||
| pyflakes = ">=2.2.0,<2.3.0" | ||||
|  | ||||
| [package.dependencies.importlib-metadata] | ||||
| version = "*" | ||||
| python = "<3.8" | ||||
|  | ||||
| [[package]] | ||||
| name = "gunicorn" | ||||
| version = "20.0.4" | ||||
| description = "WSGI HTTP Server for UNIX" | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=3.4" | ||||
|  | ||||
| [package.extras] | ||||
| eventlet = ["eventlet (>=0.9.7)"] | ||||
| gevent = ["gevent (>=0.13)"] | ||||
| setproctitle = ["setproctitle"] | ||||
| tornado = ["tornado (>=0.2)"] | ||||
|  | ||||
| [package.dependencies] | ||||
| setuptools = ">=3.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "idna" | ||||
| version = "2.10" | ||||
| description = "Internationalized Domain Names in Applications (IDNA)" | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "importlib-metadata" | ||||
| version = "3.3.0" | ||||
| description = "Read metadata from Python packages" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| marker = "python_version < \"3.8\"" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] | ||||
| testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] | ||||
|  | ||||
| [package.dependencies] | ||||
| zipp = ">=0.5" | ||||
|  | ||||
| [package.dependencies.typing-extensions] | ||||
| version = ">=3.6.4" | ||||
| python = "<3.8" | ||||
|  | ||||
| [[package]] | ||||
| name = "iniconfig" | ||||
| version = "1.1.1" | ||||
| description = "iniconfig: brain-dead simple config-ini parsing" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "ipython" | ||||
| version = "7.19.0" | ||||
| description = "IPython: Productive Interactive Computing" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [package.extras] | ||||
| all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.14)", "pygments", "qtconsole", "requests", "testpath"] | ||||
| doc = ["Sphinx (>=1.3)"] | ||||
| kernel = ["ipykernel"] | ||||
| nbconvert = ["nbconvert"] | ||||
| nbformat = ["nbformat"] | ||||
| notebook = ["notebook", "ipywidgets"] | ||||
| parallel = ["ipyparallel"] | ||||
| qtconsole = ["qtconsole"] | ||||
| test = ["nose (>=0.10.1)", "requests", "testpath", "pygments", "nbformat", "ipykernel", "numpy (>=1.14)"] | ||||
|  | ||||
| [package.dependencies] | ||||
| appnope = "*" | ||||
| backcall = "*" | ||||
| colorama = "*" | ||||
| decorator = "*" | ||||
| jedi = ">=0.10" | ||||
| pexpect = ">4.3" | ||||
| pickleshare = "*" | ||||
| prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" | ||||
| pygments = "*" | ||||
| setuptools = ">=18.5" | ||||
| traitlets = ">=4.2" | ||||
|  | ||||
| [[package]] | ||||
| name = "ipython-genutils" | ||||
| version = "0.2.0" | ||||
| description = "Vestigial utilities from IPython" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "isort" | ||||
| version = "5.6.4" | ||||
| description = "A Python utility / library to sort Python imports." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.6,<4.0" | ||||
|  | ||||
| [package.extras] | ||||
| pipfile_deprecated_finder = ["pipreqs", "requirementslib"] | ||||
| requirements_deprecated_finder = ["pipreqs", "pip-api"] | ||||
| colors = ["colorama (>=0.4.3,<0.5.0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "jedi" | ||||
| version = "0.17.2" | ||||
| description = "An autocompletion tool for Python that can be used for text editors." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [package.extras] | ||||
| qa = ["flake8 (3.7.9)"] | ||||
| testing = ["Django (<3.1)", "colorama", "docopt", "pytest (>=3.9.0,<5.0.0)"] | ||||
|  | ||||
| [package.dependencies] | ||||
| parso = ">=0.7.0,<0.8.0" | ||||
|  | ||||
| [[package]] | ||||
| name = "mccabe" | ||||
| version = "0.6.1" | ||||
| description = "McCabe checker, plugin for flake8" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "mypy-extensions" | ||||
| version = "0.4.3" | ||||
| description = "Experimental type system extensions for programs checked with the mypy typechecker." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "packaging" | ||||
| version = "20.8" | ||||
| description = "Core utilities for Python packages" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [package.dependencies] | ||||
| pyparsing = ">=2.0.2" | ||||
|  | ||||
| [[package]] | ||||
| name = "parso" | ||||
| version = "0.7.1" | ||||
| description = "A Python Parser" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [package.extras] | ||||
| testing = ["docopt", "pytest (>=3.0.7)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "pathspec" | ||||
| version = "0.8.1" | ||||
| description = "Utility library for gitignore style pattern matching of file paths." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "pexpect" | ||||
| version = "4.8.0" | ||||
| description = "Pexpect allows easy control of interactive console applications." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform != \"win32\"" | ||||
|  | ||||
| [package.dependencies] | ||||
| ptyprocess = ">=0.5" | ||||
|  | ||||
| [[package]] | ||||
| name = "pickleshare" | ||||
| version = "0.7.5" | ||||
| description = "Tiny 'shelve'-like database with concurrency support" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "pluggy" | ||||
| version = "0.13.1" | ||||
| description = "plugin and hook calling mechanisms for python" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [package.extras] | ||||
| dev = ["pre-commit", "tox"] | ||||
|  | ||||
| [package.dependencies] | ||||
| [package.dependencies.importlib-metadata] | ||||
| version = ">=0.12" | ||||
| python = "<3.8" | ||||
|  | ||||
| [[package]] | ||||
| name = "prompt-toolkit" | ||||
| version = "3.0.8" | ||||
| description = "Library for building powerful interactive command lines in Python" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.6.1" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [package.dependencies] | ||||
| wcwidth = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "psycopg2-binary" | ||||
| version = "2.8.6" | ||||
| description = "psycopg2 - Python-PostgreSQL Database Adapter" | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "ptyprocess" | ||||
| version = "0.6.0" | ||||
| description = "Run a subprocess in a pseudo terminal" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform != \"win32\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "py" | ||||
| version = "1.10.0" | ||||
| description = "library with cross-python path, ini-parsing, io, code, log facilities" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "pycodestyle" | ||||
| version = "2.6.0" | ||||
| description = "Python style guide checker" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "pyflakes" | ||||
| version = "2.2.0" | ||||
| description = "passive checker of Python programs" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "pygments" | ||||
| version = "2.7.3" | ||||
| description = "Pygments is a syntax highlighting package written in Python." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.5" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "pyparsing" | ||||
| version = "2.4.7" | ||||
| description = "Python parsing module" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "pytest" | ||||
| version = "6.2.0" | ||||
| description = "pytest: simple powerful testing with Python" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
|  | ||||
| [package.extras] | ||||
| testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] | ||||
|  | ||||
| [package.dependencies] | ||||
| atomicwrites = ">=1.0" | ||||
| attrs = ">=19.2.0" | ||||
| colorama = "*" | ||||
| iniconfig = "*" | ||||
| packaging = "*" | ||||
| pluggy = ">=0.12,<1.0.0a1" | ||||
| py = ">=1.8.2" | ||||
| toml = "*" | ||||
|  | ||||
| [package.dependencies.importlib-metadata] | ||||
| version = ">=0.12" | ||||
| python = "<3.8" | ||||
|  | ||||
| [[package]] | ||||
| name = "regex" | ||||
| version = "2020.11.13" | ||||
| description = "Alternative regular expression module, to replace re." | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "requests" | ||||
| version = "2.25.0" | ||||
| description = "Python HTTP for Humans." | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" | ||||
|  | ||||
| [package.extras] | ||||
| security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] | ||||
| socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"] | ||||
|  | ||||
| [package.dependencies] | ||||
| certifi = ">=2017.4.17" | ||||
| chardet = ">=3.0.2,<4" | ||||
| idna = ">=2.5,<3" | ||||
| urllib3 = ">=1.21.1,<1.27" | ||||
|  | ||||
| [[package]] | ||||
| name = "toml" | ||||
| version = "0.10.2" | ||||
| description = "Python Library for Tom's Obvious, Minimal Language" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" | ||||
|  | ||||
| [[package]] | ||||
| name = "traitlets" | ||||
| version = "5.0.5" | ||||
| description = "Traitlets Python configuration system" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.7" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [package.extras] | ||||
| test = ["pytest"] | ||||
|  | ||||
| [package.dependencies] | ||||
| ipython-genutils = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "typed-ast" | ||||
| version = "1.4.1" | ||||
| description = "a fork of Python 2 and 3 ast modules with type comment support" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "typing-extensions" | ||||
| version = "3.7.4.3" | ||||
| description = "Backported and Experimental Type Hints for Python 3.5+" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
|  | ||||
| [[package]] | ||||
| name = "urllib3" | ||||
| version = "1.26.2" | ||||
| description = "HTTP library with thread-safe connection pooling, file post, and more." | ||||
| category = "main" | ||||
| optional = false | ||||
| python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" | ||||
|  | ||||
| [package.extras] | ||||
| brotli = ["brotlipy (>=0.6.0)"] | ||||
| secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] | ||||
| socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] | ||||
|  | ||||
| [[package]] | ||||
| name = "wcwidth" | ||||
| version = "0.2.5" | ||||
| description = "Measures the displayed width of unicode strings in a terminal" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = "*" | ||||
| marker = "python_version >= \"3.7\" and python_version < \"4.0\"" | ||||
|  | ||||
| [[package]] | ||||
| name = "zipp" | ||||
| version = "3.4.0" | ||||
| description = "Backport of pathlib-compatible object wrapper for zip files" | ||||
| category = "dev" | ||||
| optional = false | ||||
| python-versions = ">=3.6" | ||||
| marker = "python_version < \"3.8\"" | ||||
|  | ||||
| [package.extras] | ||||
| docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] | ||||
| testing = ["pytest (>=3.5,<3.7.3 || >3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake8", "pytest-cov", "jaraco.test (>=3.2.0)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] | ||||
|  | ||||
| [metadata] | ||||
| lock-version = "1.0" | ||||
| python-versions = "^3.6" | ||||
| content-hash = "3cd45aacbfab0e85f74c7a010443432f4d6bf0f6cd3bbb84e11c8c7ea20a4613" | ||||
|  | ||||
| [metadata.files] | ||||
| appdirs = [ | ||||
|     {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, | ||||
|     {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, | ||||
| ] | ||||
| appnope = [ | ||||
|     {file = "appnope-0.1.2-py2.py3-none-any.whl", hash = "sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442"}, | ||||
|     {file = "appnope-0.1.2.tar.gz", hash = "sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a"}, | ||||
| ] | ||||
| atomicwrites = [ | ||||
|     {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, | ||||
|     {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, | ||||
| ] | ||||
| attrs = [ | ||||
|     {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, | ||||
|     {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, | ||||
| ] | ||||
| backcall = [ | ||||
|     {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, | ||||
|     {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, | ||||
| ] | ||||
| black = [ | ||||
|     {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, | ||||
| ] | ||||
| certifi = [ | ||||
|     {file = "certifi-2020.12.5-py2.py3-none-any.whl", hash = "sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830"}, | ||||
|     {file = "certifi-2020.12.5.tar.gz", hash = "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c"}, | ||||
| ] | ||||
| chardet = [ | ||||
|     {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, | ||||
|     {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, | ||||
| ] | ||||
| click = [ | ||||
|     {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, | ||||
|     {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, | ||||
| ] | ||||
| colorama = [ | ||||
|     {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, | ||||
|     {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, | ||||
| ] | ||||
| dataclasses = [ | ||||
|     {file = "dataclasses-0.6-py3-none-any.whl", hash = "sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f"}, | ||||
|     {file = "dataclasses-0.6.tar.gz", hash = "sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84"}, | ||||
| ] | ||||
| decorator = [ | ||||
|     {file = "decorator-4.4.2-py2.py3-none-any.whl", hash = "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760"}, | ||||
|     {file = "decorator-4.4.2.tar.gz", hash = "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"}, | ||||
| ] | ||||
| falcon = [ | ||||
|     {file = "falcon-2.0.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983"}, | ||||
|     {file = "falcon-2.0.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b"}, | ||||
|     {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389"}, | ||||
|     {file = "falcon-2.0.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936"}, | ||||
|     {file = "falcon-2.0.0-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8"}, | ||||
|     {file = "falcon-2.0.0-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986"}, | ||||
|     {file = "falcon-2.0.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439"}, | ||||
|     {file = "falcon-2.0.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4"}, | ||||
|     {file = "falcon-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad"}, | ||||
|     {file = "falcon-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494"}, | ||||
|     {file = "falcon-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357"}, | ||||
|     {file = "falcon-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9"}, | ||||
|     {file = "falcon-2.0.0-py2.py3-none-any.whl", hash = "sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53"}, | ||||
|     {file = "falcon-2.0.0.tar.gz", hash = "sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc"}, | ||||
| ] | ||||
| flake8 = [ | ||||
|     {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, | ||||
|     {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, | ||||
| ] | ||||
| gunicorn = [ | ||||
|     {file = "gunicorn-20.0.4-py2.py3-none-any.whl", hash = "sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c"}, | ||||
|     {file = "gunicorn-20.0.4.tar.gz", hash = "sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626"}, | ||||
| ] | ||||
| idna = [ | ||||
|     {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"}, | ||||
|     {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"}, | ||||
| ] | ||||
| importlib-metadata = [ | ||||
|     {file = "importlib_metadata-3.3.0-py3-none-any.whl", hash = "sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450"}, | ||||
|     {file = "importlib_metadata-3.3.0.tar.gz", hash = "sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed"}, | ||||
| ] | ||||
| iniconfig = [ | ||||
|     {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, | ||||
|     {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, | ||||
| ] | ||||
| ipython = [ | ||||
|     {file = "ipython-7.19.0-py3-none-any.whl", hash = "sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f"}, | ||||
|     {file = "ipython-7.19.0.tar.gz", hash = "sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a"}, | ||||
| ] | ||||
| ipython-genutils = [ | ||||
|     {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, | ||||
|     {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, | ||||
| ] | ||||
| isort = [ | ||||
|     {file = "isort-5.6.4-py3-none-any.whl", hash = "sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7"}, | ||||
|     {file = "isort-5.6.4.tar.gz", hash = "sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58"}, | ||||
| ] | ||||
| jedi = [ | ||||
|     {file = "jedi-0.17.2-py2.py3-none-any.whl", hash = "sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5"}, | ||||
|     {file = "jedi-0.17.2.tar.gz", hash = "sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20"}, | ||||
| ] | ||||
| mccabe = [ | ||||
|     {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, | ||||
|     {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, | ||||
| ] | ||||
| mypy-extensions = [ | ||||
|     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, | ||||
|     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, | ||||
| ] | ||||
| packaging = [ | ||||
|     {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, | ||||
|     {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, | ||||
| ] | ||||
| parso = [ | ||||
|     {file = "parso-0.7.1-py2.py3-none-any.whl", hash = "sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea"}, | ||||
|     {file = "parso-0.7.1.tar.gz", hash = "sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9"}, | ||||
| ] | ||||
| pathspec = [ | ||||
|     {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, | ||||
|     {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, | ||||
| ] | ||||
| pexpect = [ | ||||
|     {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, | ||||
|     {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, | ||||
| ] | ||||
| pickleshare = [ | ||||
|     {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, | ||||
|     {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, | ||||
| ] | ||||
| pluggy = [ | ||||
|     {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, | ||||
|     {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, | ||||
| ] | ||||
| prompt-toolkit = [ | ||||
|     {file = "prompt_toolkit-3.0.8-py3-none-any.whl", hash = "sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63"}, | ||||
|     {file = "prompt_toolkit-3.0.8.tar.gz", hash = "sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c"}, | ||||
| ] | ||||
| psycopg2-binary = [ | ||||
|     {file = "psycopg2-binary-2.8.6.tar.gz", hash = "sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27m-win32.whl", hash = "sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27m-win_amd64.whl", hash = "sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp34-cp34m-win32.whl", hash = "sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp34-cp34m-win_amd64.whl", hash = "sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp35-cp35m-win32.whl", hash = "sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp35-cp35m-win_amd64.whl", hash = "sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp36-cp36m-win32.whl", hash = "sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp36-cp36m-win_amd64.whl", hash = "sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp37-cp37m-win32.whl", hash = "sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp37-cp37m-win_amd64.whl", hash = "sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp38-cp38-win32.whl", hash = "sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp38-cp38-win_amd64.whl", hash = "sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52"}, | ||||
|     {file = "psycopg2_binary-2.8.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd"}, | ||||
| ] | ||||
| ptyprocess = [ | ||||
|     {file = "ptyprocess-0.6.0-py2.py3-none-any.whl", hash = "sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f"}, | ||||
|     {file = "ptyprocess-0.6.0.tar.gz", hash = "sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0"}, | ||||
| ] | ||||
| py = [ | ||||
|     {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, | ||||
|     {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, | ||||
| ] | ||||
| pycodestyle = [ | ||||
|     {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, | ||||
|     {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, | ||||
| ] | ||||
| pyflakes = [ | ||||
|     {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, | ||||
|     {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, | ||||
| ] | ||||
| pygments = [ | ||||
|     {file = "Pygments-2.7.3-py3-none-any.whl", hash = "sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08"}, | ||||
|     {file = "Pygments-2.7.3.tar.gz", hash = "sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716"}, | ||||
| ] | ||||
| pyparsing = [ | ||||
|     {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, | ||||
|     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, | ||||
| ] | ||||
| pytest = [ | ||||
|     {file = "pytest-6.2.0-py3-none-any.whl", hash = "sha256:d69e1a80b34fe4d596c9142f35d9e523d98a2838976f1a68419a8f051b24cec6"}, | ||||
|     {file = "pytest-6.2.0.tar.gz", hash = "sha256:b12e09409c5bdedc28d308469e156127004a436b41e9b44f9bff6446cbab9152"}, | ||||
| ] | ||||
| regex = [ | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, | ||||
|     {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, | ||||
|     {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, | ||||
|     {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, | ||||
|     {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, | ||||
|     {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, | ||||
| ] | ||||
| requests = [ | ||||
|     {file = "requests-2.25.0-py2.py3-none-any.whl", hash = "sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998"}, | ||||
|     {file = "requests-2.25.0.tar.gz", hash = "sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8"}, | ||||
| ] | ||||
| toml = [ | ||||
|     {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, | ||||
|     {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, | ||||
| ] | ||||
| traitlets = [ | ||||
|     {file = "traitlets-5.0.5-py3-none-any.whl", hash = "sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426"}, | ||||
|     {file = "traitlets-5.0.5.tar.gz", hash = "sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396"}, | ||||
| ] | ||||
| typed-ast = [ | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, | ||||
|     {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, | ||||
|     {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, | ||||
|     {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, | ||||
|     {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, | ||||
|     {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, | ||||
|     {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, | ||||
| ] | ||||
| typing-extensions = [ | ||||
|     {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, | ||||
|     {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, | ||||
|     {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, | ||||
| ] | ||||
| urllib3 = [ | ||||
|     {file = "urllib3-1.26.2-py2.py3-none-any.whl", hash = "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473"}, | ||||
|     {file = "urllib3-1.26.2.tar.gz", hash = "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08"}, | ||||
| ] | ||||
| wcwidth = [ | ||||
|     {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, | ||||
|     {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, | ||||
| ] | ||||
| zipp = [ | ||||
|     {file = "zipp-3.4.0-py3-none-any.whl", hash = "sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108"}, | ||||
|     {file = "zipp-3.4.0.tar.gz", hash = "sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb"}, | ||||
| ] | ||||
							
								
								
									
										24
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pyproject.toml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| [tool.poetry] | ||||
| name = "dspace-statistics-api" | ||||
| version = "1.3.2" | ||||
| description = "A simple REST API to expose Solr view and download statistics for items in a DSpace repository." | ||||
| authors = ["Alan Orth <aorth@mjanja.ch>"] | ||||
| license = "GPL-3.0-only" | ||||
|  | ||||
| [tool.poetry.dependencies] | ||||
| python = "^3.6" | ||||
| gunicorn = "^20.0.4" | ||||
| falcon = "^2.0.0" | ||||
| psycopg2-binary = "^2.8.6" | ||||
| requests = "^2.24.0" | ||||
|  | ||||
| [tool.poetry.dev-dependencies] | ||||
| ipython = { version = "^7.18.1", python = "^3.7" } | ||||
| flake8 = "^3.8.4" | ||||
| pytest = "^6.1.1" | ||||
| isort = "^5.5.4" | ||||
| black = "^20.8b1" | ||||
|  | ||||
| [build-system] | ||||
| requires = ["poetry>=0.12"] | ||||
| build-backend = "poetry.masonry.api" | ||||
							
								
								
									
										4
									
								
								pytest.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								pytest.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| [pytest] | ||||
| addopts= -rsxX -s -v --strict-markers | ||||
| filterwarnings = | ||||
|     error::UserWarning | ||||
							
								
								
									
										244
									
								
								requirements-dev.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										244
									
								
								requirements-dev.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,244 @@ | ||||
| appdirs==1.4.4 \ | ||||
|     --hash=sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128 \ | ||||
|     --hash=sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41 | ||||
| appnope==0.1.2; python_version >= "3.7" and python_version < "4.0" and sys_platform == "darwin" \ | ||||
|     --hash=sha256:93aa393e9d6c54c5cd570ccadd8edad61ea0c4b9ea7a01409020c9aa019eb442 \ | ||||
|     --hash=sha256:dd83cd4b5b460958838f6eb3000c660b1f9caf2a5b1de4264e941512f603258a | ||||
| atomicwrites==1.4.0; sys_platform == "win32" \ | ||||
|     --hash=sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197 \ | ||||
|     --hash=sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a | ||||
| attrs==20.3.0 \ | ||||
|     --hash=sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6 \ | ||||
|     --hash=sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700 | ||||
| backcall==0.2.0; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255 \ | ||||
|     --hash=sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e | ||||
| black==20.8b1 \ | ||||
|     --hash=sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea | ||||
| certifi==2020.12.5 \ | ||||
|     --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ | ||||
|     --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c | ||||
| chardet==3.0.4 \ | ||||
|     --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ | ||||
|     --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae | ||||
| click==7.1.2 \ | ||||
|     --hash=sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc \ | ||||
|     --hash=sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a | ||||
| colorama==0.4.4; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32" or sys_platform == "win32" \ | ||||
|     --hash=sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2 \ | ||||
|     --hash=sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b | ||||
| dataclasses==0.6; python_version < "3.7" \ | ||||
|     --hash=sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f \ | ||||
|     --hash=sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84 | ||||
| decorator==4.4.2; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760 \ | ||||
|     --hash=sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7 | ||||
| falcon==2.0.0 \ | ||||
|     --hash=sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983 \ | ||||
|     --hash=sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b \ | ||||
|     --hash=sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389 \ | ||||
|     --hash=sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936 \ | ||||
|     --hash=sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8 \ | ||||
|     --hash=sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986 \ | ||||
|     --hash=sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439 \ | ||||
|     --hash=sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4 \ | ||||
|     --hash=sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad \ | ||||
|     --hash=sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494 \ | ||||
|     --hash=sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357 \ | ||||
|     --hash=sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9 \ | ||||
|     --hash=sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53 \ | ||||
|     --hash=sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc | ||||
| flake8==3.8.4 \ | ||||
|     --hash=sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839 \ | ||||
|     --hash=sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b | ||||
| gunicorn==20.0.4 \ | ||||
|     --hash=sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \ | ||||
|     --hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626 | ||||
| idna==2.10 \ | ||||
|     --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ | ||||
|     --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 | ||||
| importlib-metadata==3.3.0; python_version < "3.8" \ | ||||
|     --hash=sha256:bf792d480abbd5eda85794e4afb09dd538393f7d6e6ffef6e9f03d2014cf9450 \ | ||||
|     --hash=sha256:5c5a2720817414a6c41f0a49993908068243ae02c1635a228126519b509c8aed | ||||
| iniconfig==1.1.1 \ | ||||
|     --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ | ||||
|     --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 | ||||
| ipython==7.19.0; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:c987e8178ced651532b3b1ff9965925bfd445c279239697052561a9ab806d28f \ | ||||
|     --hash=sha256:cbb2ef3d5961d44e6a963b9817d4ea4e1fa2eb589c371a470fed14d8d40cbd6a | ||||
| ipython-genutils==0.2.0; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8 \ | ||||
|     --hash=sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8 | ||||
| isort==5.6.4 \ | ||||
|     --hash=sha256:dcab1d98b469a12a1a624ead220584391648790275560e1a43e54c5dceae65e7 \ | ||||
|     --hash=sha256:dcaeec1b5f0eca77faea2a35ab790b4f3680ff75590bfcb7145986905aab2f58 | ||||
| jedi==0.17.2; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:98cc583fa0f2f8304968199b01b6b4b94f469a1f4a74c1560506ca2a211378b5 \ | ||||
|     --hash=sha256:86ed7d9b750603e4ba582ea8edc678657fb4007894a12bcf6f4bb97892f31d20 | ||||
| mccabe==0.6.1 \ | ||||
|     --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \ | ||||
|     --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f | ||||
| mypy-extensions==0.4.3 \ | ||||
|     --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ | ||||
|     --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 | ||||
| packaging==20.8 \ | ||||
|     --hash=sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858 \ | ||||
|     --hash=sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093 | ||||
| parso==0.7.1; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:97218d9159b2520ff45eb78028ba8b50d2bc61dcc062a9682666f2dc4bd331ea \ | ||||
|     --hash=sha256:caba44724b994a8a5e086460bb212abc5a8bc46951bf4a9a1210745953622eb9 | ||||
| pathspec==0.8.1 \ | ||||
|     --hash=sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d \ | ||||
|     --hash=sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd | ||||
| pexpect==4.8.0; python_version >= "3.7" and python_version < "4.0" and sys_platform != "win32" \ | ||||
|     --hash=sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937 \ | ||||
|     --hash=sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c | ||||
| pickleshare==0.7.5; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56 \ | ||||
|     --hash=sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca | ||||
| pluggy==0.13.1 \ | ||||
|     --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d \ | ||||
|     --hash=sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0 | ||||
| prompt-toolkit==3.0.8; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:7debb9a521e0b1ee7d2fe96ee4bd60ef03c6492784de0547337ca4433e46aa63 \ | ||||
|     --hash=sha256:25c95d2ac813909f813c93fde734b6e44406d1477a9faef7c915ff37d39c0a8c | ||||
| psycopg2-binary==2.8.6 \ | ||||
|     --hash=sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0 \ | ||||
|     --hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \ | ||||
|     --hash=sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db \ | ||||
|     --hash=sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5 \ | ||||
|     --hash=sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25 \ | ||||
|     --hash=sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c \ | ||||
|     --hash=sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c \ | ||||
|     --hash=sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1 \ | ||||
|     --hash=sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2 \ | ||||
|     --hash=sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152 \ | ||||
|     --hash=sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449 \ | ||||
|     --hash=sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859 \ | ||||
|     --hash=sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550 \ | ||||
|     --hash=sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd \ | ||||
|     --hash=sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71 \ | ||||
|     --hash=sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4 \ | ||||
|     --hash=sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb \ | ||||
|     --hash=sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da \ | ||||
|     --hash=sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2 \ | ||||
|     --hash=sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a \ | ||||
|     --hash=sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679 \ | ||||
|     --hash=sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf \ | ||||
|     --hash=sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b \ | ||||
|     --hash=sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67 \ | ||||
|     --hash=sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66 \ | ||||
|     --hash=sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f \ | ||||
|     --hash=sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77 \ | ||||
|     --hash=sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94 \ | ||||
|     --hash=sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729 \ | ||||
|     --hash=sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77 \ | ||||
|     --hash=sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52 \ | ||||
|     --hash=sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd | ||||
| ptyprocess==0.6.0; python_version >= "3.7" and python_version < "4.0" and sys_platform != "win32" \ | ||||
|     --hash=sha256:d7cc528d76e76342423ca640335bd3633420dc1366f258cb31d05e865ef5ca1f \ | ||||
|     --hash=sha256:923f299cc5ad920c68f2bc0bc98b75b9f838b93b599941a6b63ddbc2476394c0 | ||||
| py==1.10.0 \ | ||||
|     --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a \ | ||||
|     --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 | ||||
| pycodestyle==2.6.0 \ | ||||
|     --hash=sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367 \ | ||||
|     --hash=sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e | ||||
| pyflakes==2.2.0 \ | ||||
|     --hash=sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92 \ | ||||
|     --hash=sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8 | ||||
| pygments==2.7.3; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:f275b6c0909e5dafd2d6269a656aa90fa58ebf4a74f8fcf9053195d226b24a08 \ | ||||
|     --hash=sha256:ccf3acacf3782cbed4a989426012f1c535c9a90d3a7fc3f16d231b9372d2b716 | ||||
| pyparsing==2.4.7 \ | ||||
|     --hash=sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b \ | ||||
|     --hash=sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1 | ||||
| pytest==6.2.0 \ | ||||
|     --hash=sha256:d69e1a80b34fe4d596c9142f35d9e523d98a2838976f1a68419a8f051b24cec6 \ | ||||
|     --hash=sha256:b12e09409c5bdedc28d308469e156127004a436b41e9b44f9bff6446cbab9152 | ||||
| regex==2020.11.13 \ | ||||
|     --hash=sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85 \ | ||||
|     --hash=sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70 \ | ||||
|     --hash=sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee \ | ||||
|     --hash=sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5 \ | ||||
|     --hash=sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7 \ | ||||
|     --hash=sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31 \ | ||||
|     --hash=sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa \ | ||||
|     --hash=sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6 \ | ||||
|     --hash=sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e \ | ||||
|     --hash=sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884 \ | ||||
|     --hash=sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b \ | ||||
|     --hash=sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88 \ | ||||
|     --hash=sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0 \ | ||||
|     --hash=sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1 \ | ||||
|     --hash=sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0 \ | ||||
|     --hash=sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512 \ | ||||
|     --hash=sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba \ | ||||
|     --hash=sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538 \ | ||||
|     --hash=sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4 \ | ||||
|     --hash=sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444 \ | ||||
|     --hash=sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f \ | ||||
|     --hash=sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d \ | ||||
|     --hash=sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af \ | ||||
|     --hash=sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f \ | ||||
|     --hash=sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b \ | ||||
|     --hash=sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8 \ | ||||
|     --hash=sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5 \ | ||||
|     --hash=sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b \ | ||||
|     --hash=sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c \ | ||||
|     --hash=sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683 \ | ||||
|     --hash=sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc \ | ||||
|     --hash=sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364 \ | ||||
|     --hash=sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e \ | ||||
|     --hash=sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e \ | ||||
|     --hash=sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917 \ | ||||
|     --hash=sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b \ | ||||
|     --hash=sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9 \ | ||||
|     --hash=sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c \ | ||||
|     --hash=sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f \ | ||||
|     --hash=sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d \ | ||||
|     --hash=sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562 | ||||
| requests==2.25.0 \ | ||||
|     --hash=sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998 \ | ||||
|     --hash=sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8 | ||||
| toml==0.10.2 \ | ||||
|     --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ | ||||
|     --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f | ||||
| traitlets==5.0.5; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:69ff3f9d5351f31a7ad80443c2674b7099df13cc41fc5fa6e2f6d3b0330b0426 \ | ||||
|     --hash=sha256:178f4ce988f69189f7e523337a3e11d91c786ded9360174a3d9ca83e79bc5396 | ||||
| typed-ast==1.4.1 \ | ||||
|     --hash=sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3 \ | ||||
|     --hash=sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb \ | ||||
|     --hash=sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919 \ | ||||
|     --hash=sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01 \ | ||||
|     --hash=sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75 \ | ||||
|     --hash=sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652 \ | ||||
|     --hash=sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7 \ | ||||
|     --hash=sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1 \ | ||||
|     --hash=sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa \ | ||||
|     --hash=sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614 \ | ||||
|     --hash=sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41 \ | ||||
|     --hash=sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b \ | ||||
|     --hash=sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe \ | ||||
|     --hash=sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355 \ | ||||
|     --hash=sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6 \ | ||||
|     --hash=sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907 \ | ||||
|     --hash=sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d \ | ||||
|     --hash=sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c \ | ||||
|     --hash=sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4 \ | ||||
|     --hash=sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34 \ | ||||
|     --hash=sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b | ||||
| typing-extensions==3.7.4.3 \ | ||||
|     --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f \ | ||||
|     --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ | ||||
|     --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c | ||||
| urllib3==1.26.2 \ | ||||
|     --hash=sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473 \ | ||||
|     --hash=sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08 | ||||
| wcwidth==0.2.5; python_version >= "3.7" and python_version < "4.0" \ | ||||
|     --hash=sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784 \ | ||||
|     --hash=sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83 | ||||
| zipp==3.4.0; python_version < "3.8" \ | ||||
|     --hash=sha256:102c24ef8f171fd729d46599845e95c7ab894a4cf45f5de11a44cc7444fb1108 \ | ||||
|     --hash=sha256:ed5eee1974372595f9e416cc7bbeeb12335201d8081ca8a0743c954d4446e5cb | ||||
							
								
								
									
										66
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| certifi==2020.12.5 \ | ||||
|     --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ | ||||
|     --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c | ||||
| chardet==3.0.4 \ | ||||
|     --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 \ | ||||
|     --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae | ||||
| falcon==2.0.0 \ | ||||
|     --hash=sha256:733033ec80c896e30a43ab3e776856096836787197a44eb21022320a61311983 \ | ||||
|     --hash=sha256:f93351459f110b4c1ee28556aef9a791832df6f910bea7b3f616109d534df06b \ | ||||
|     --hash=sha256:e9efa0791b5d9f9dd9689015ea6bce0a27fcd5ecbcd30e6d940bffa4f7f03389 \ | ||||
|     --hash=sha256:59d1e8c993b9a37ea06df9d72cf907a46cc8063b30717cdac2f34d1658b6f936 \ | ||||
|     --hash=sha256:a5ebb22a04c9cc65081938ee7651b4e3b4d2a28522ea8ec04c7bdd2b3e9e8cd8 \ | ||||
|     --hash=sha256:95bf6ce986c1119aef12c9b348f4dee9c6dcc58391bdd0bc2b0bf353c2b15986 \ | ||||
|     --hash=sha256:aa184895d1ad4573fbfaaf803563d02f019ebdf4790e41cc568a330607eae439 \ | ||||
|     --hash=sha256:74cf1d18207381c665b9e6292d65100ce146d958707793174b03869dc6e614f4 \ | ||||
|     --hash=sha256:24adcd2b29a8ffa9d552dc79638cd21736a3fb04eda7d102c6cebafdaadb88ad \ | ||||
|     --hash=sha256:18157af2a4fc3feedf2b5dcc6196f448639acf01c68bc33d4d5a04c3ef87f494 \ | ||||
|     --hash=sha256:e3782b7b92fefd46a6ad1fd8fe63fe6c6f1b7740a95ca56957f48d1aee34b357 \ | ||||
|     --hash=sha256:9712975adcf8c6e12876239085ad757b8fdeba223d46d23daef82b47658f83a9 \ | ||||
|     --hash=sha256:54f2cb4b687035b2a03206dbfc538055cc48b59a953187b0458aa1b574d47b53 \ | ||||
|     --hash=sha256:eea593cf466b9c126ce667f6d30503624ef24459f118c75594a69353b6c3d5fc | ||||
| gunicorn==20.0.4 \ | ||||
|     --hash=sha256:cd4a810dd51bf497552cf3f863b575dabd73d6ad6a91075b65936b151cbf4f9c \ | ||||
|     --hash=sha256:1904bb2b8a43658807108d59c3f3d56c2b6121a701161de0ddf9ad140073c626 | ||||
| idna==2.10 \ | ||||
|     --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ | ||||
|     --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 | ||||
| psycopg2-binary==2.8.6 \ | ||||
|     --hash=sha256:11b9c0ebce097180129e422379b824ae21c8f2a6596b159c7659e2e5a00e1aa0 \ | ||||
|     --hash=sha256:d14b140a4439d816e3b1229a4a525df917d6ea22a0771a2a78332273fd9528a4 \ | ||||
|     --hash=sha256:1fabed9ea2acc4efe4671b92c669a213db744d2af8a9fc5d69a8e9bc14b7a9db \ | ||||
|     --hash=sha256:f5ab93a2cb2d8338b1674be43b442a7f544a0971da062a5da774ed40587f18f5 \ | ||||
|     --hash=sha256:b4afc542c0ac0db720cf516dd20c0846f71c248d2b3d21013aa0d4ef9c71ca25 \ | ||||
|     --hash=sha256:e74a55f6bad0e7d3968399deb50f61f4db1926acf4a6d83beaaa7df986f48b1c \ | ||||
|     --hash=sha256:0deac2af1a587ae12836aa07970f5cb91964f05a7c6cdb69d8425ff4c15d4e2c \ | ||||
|     --hash=sha256:ad20d2eb875aaa1ea6d0f2916949f5c08a19c74d05b16ce6ebf6d24f2c9f75d1 \ | ||||
|     --hash=sha256:950bc22bb56ee6ff142a2cb9ee980b571dd0912b0334aa3fe0fe3788d860bea2 \ | ||||
|     --hash=sha256:b8a3715b3c4e604bcc94c90a825cd7f5635417453b253499664f784fc4da0152 \ | ||||
|     --hash=sha256:d1b4ab59e02d9008efe10ceabd0b31e79519da6fb67f7d8e8977118832d0f449 \ | ||||
|     --hash=sha256:ac0c682111fbf404525dfc0f18a8b5f11be52657d4f96e9fcb75daf4f3984859 \ | ||||
|     --hash=sha256:7d92a09b788cbb1aec325af5fcba9fed7203897bbd9269d5691bb1e3bce29550 \ | ||||
|     --hash=sha256:aaa4213c862f0ef00022751161df35804127b78adf4a2755b9f991a507e425fd \ | ||||
|     --hash=sha256:c2507d796fca339c8fb03216364cca68d87e037c1f774977c8fc377627d01c71 \ | ||||
|     --hash=sha256:ee69dad2c7155756ad114c02db06002f4cded41132cc51378e57aad79cc8e4f4 \ | ||||
|     --hash=sha256:e82aba2188b9ba309fd8e271702bd0d0fc9148ae3150532bbb474f4590039ffb \ | ||||
|     --hash=sha256:d5227b229005a696cc67676e24c214740efd90b148de5733419ac9aaba3773da \ | ||||
|     --hash=sha256:a0eb43a07386c3f1f1ebb4dc7aafb13f67188eab896e7397aa1ee95a9c884eb2 \ | ||||
|     --hash=sha256:e1f57aa70d3f7cc6947fd88636a481638263ba04a742b4a37dd25c373e41491a \ | ||||
|     --hash=sha256:833709a5c66ca52f1d21d41865a637223b368c0ee76ea54ca5bad6f2526c7679 \ | ||||
|     --hash=sha256:ba28584e6bca48c59eecbf7efb1576ca214b47f05194646b081717fa628dfddf \ | ||||
|     --hash=sha256:6a32f3a4cb2f6e1a0b15215f448e8ce2da192fd4ff35084d80d5e39da683e79b \ | ||||
|     --hash=sha256:0e4dc3d5996760104746e6cfcdb519d9d2cd27c738296525d5867ea695774e67 \ | ||||
|     --hash=sha256:cec7e622ebc545dbb4564e483dd20e4e404da17ae07e06f3e780b2dacd5cee66 \ | ||||
|     --hash=sha256:ba381aec3a5dc29634f20692349d73f2d21f17653bda1decf0b52b11d694541f \ | ||||
|     --hash=sha256:a0c50db33c32594305b0ef9abc0cb7db13de7621d2cadf8392a1d9b3c437ef77 \ | ||||
|     --hash=sha256:2dac98e85565d5688e8ab7bdea5446674a83a3945a8f416ad0110018d1501b94 \ | ||||
|     --hash=sha256:bd1be66dde2b82f80afb9459fc618216753f67109b859a361cf7def5c7968729 \ | ||||
|     --hash=sha256:8cd0fb36c7412996859cb4606a35969dd01f4ea34d9812a141cd920c3b18be77 \ | ||||
|     --hash=sha256:42ec1035841b389e8cc3692277a0bd81cdfe0b65d575a2c8862cec7a80e62e52 \ | ||||
|     --hash=sha256:7312e931b90fe14f925729cde58022f5d034241918a5c4f9797cac62f6b3a9dd | ||||
| requests==2.25.0 \ | ||||
|     --hash=sha256:e786fa28d8c9154e6a4de5d46a1d921b8749f8b74e28bde23768e5e16eece998 \ | ||||
|     --hash=sha256:7f1a0b932f4a60a1a65caa4263921bb7d9ee911957e0ae4a23a6dd08185ad5f8 | ||||
| urllib3==1.26.2 \ | ||||
|     --hash=sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473 \ | ||||
|     --hash=sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08 | ||||
							
								
								
									
										6
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								setup.cfg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| [isort] | ||||
| multi_line_output=3 | ||||
| include_trailing_comma=True | ||||
| force_grid_wrap=0 | ||||
| use_parentheses=True | ||||
| line_length=88 | ||||
							
								
								
									
										0
									
								
								tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										724
									
								
								tests/dspacestatistics.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										724
									
								
								tests/dspacestatistics.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,724 @@ | ||||
| -- | ||||
| -- PostgreSQL database dump | ||||
| -- | ||||
|  | ||||
| -- Dumped from database version 10.15 (Ubuntu 10.15-1.pgdg18.04+1) | ||||
| -- Dumped by pg_dump version 10.15 (Ubuntu 10.15-1.pgdg18.04+1) | ||||
|  | ||||
| SET statement_timeout = 0; | ||||
| SET lock_timeout = 0; | ||||
| SET idle_in_transaction_session_timeout = 0; | ||||
| SET client_encoding = 'UTF8'; | ||||
| SET standard_conforming_strings = on; | ||||
| SELECT pg_catalog.set_config('search_path', '', false); | ||||
| SET check_function_bodies = false; | ||||
| SET xmloption = content; | ||||
| SET client_min_messages = warning; | ||||
| SET row_security = off; | ||||
|  | ||||
| -- | ||||
| -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:  | ||||
| -- | ||||
|  | ||||
| CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:  | ||||
| -- | ||||
|  | ||||
| COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; | ||||
|  | ||||
|  | ||||
| SET default_tablespace = ''; | ||||
|  | ||||
| SET default_with_oids = false; | ||||
|  | ||||
| -- | ||||
| -- Name: items; Type: TABLE; Schema: public; Owner: dspacestatistics | ||||
| -- | ||||
|  | ||||
| CREATE TABLE public.items ( | ||||
|     id uuid NOT NULL, | ||||
|     views integer DEFAULT 0, | ||||
|     downloads integer DEFAULT 0 | ||||
| ); | ||||
|  | ||||
|  | ||||
| ALTER TABLE public.items OWNER TO dspacestatistics; | ||||
|  | ||||
| -- | ||||
| -- Data for Name: items; Type: TABLE DATA; Schema: public; Owner: dspacestatistics | ||||
| -- | ||||
|  | ||||
| COPY public.items (id, views, downloads) FROM stdin; | ||||
| 8337cf0b-4215-4391-8ba5-72f0d6532417	99	105 | ||||
| b41596f8-2986-4285-b409-893cb69eda25	799	101 | ||||
| dfb10a04-71e4-47d5-9896-a60ccb5ea025	766	95 | ||||
| b3a431f6-8da1-4efb-b162-feed75de7e6f	95	105 | ||||
| 310b0efd-3086-4d27-9602-4e3f39a386ef	101	163 | ||||
| c24eec45-d311-4208-8009-ec5fe04a97c4	21	13 | ||||
| dc261c4c-9219-4419-a583-d60b93d12b0f	20	0 | ||||
| 9c61859f-96b6-4392-a838-d0f97bb6e2e7	47	0 | ||||
| f29c704d-5c3e-43f1-ae97-8e06895b5fd7	156	0 | ||||
| 3583e3ce-c2b4-481a-bb49-8c901bbd5dbc	90	108 | ||||
| 20d6a78a-53a7-4f95-ac43-0cac094be09b	23	0 | ||||
| 8fda532b-af3a-4099-a5b2-f79ab2476bf2	36	0 | ||||
| 279a7025-3ea5-4d2b-9ffa-d8fb449945e1	152	0 | ||||
| 423d54b4-533e-42e4-96ef-9a4f1b7c5717	23	0 | ||||
| 5dee78a6-22d5-4c5c-965b-6c61d0baa4f6	26	21 | ||||
| eb4223be-18b7-40eb-bde8-faf26f350bcf	21	0 | ||||
| dc50b82c-29cf-42a0-b39f-eaabe8c20ab5	20	0 | ||||
| ef8cd486-ec2c-43f1-943f-aa77ce3d21c0	46	105 | ||||
| 844180f2-9b7b-451a-81a1-776aaea39c23	120	108 | ||||
| 8810a970-c2f9-4132-a0c1-841e0a08b35e	35	0 | ||||
| fd8a46d5-1480-4e69-b187-cd3db96d8e4d	105	105 | ||||
| 8f90b3df-3db8-4f96-bfe9-989c62302092	28	0 | ||||
| 6b8b0ac8-3b67-47b1-aa0a-5e3c91f37b7b	48	0 | ||||
| 673c3414-ca87-4510-82d8-e59dea605c14	21	9 | ||||
| e0e42cc1-3804-4e5f-a220-87b87deb3f45	20	19 | ||||
| 36830fe2-4162-4d58-a3bd-787ccb9f5163	23	17 | ||||
| 69c501ac-4131-47a7-a682-5cac29e0d86a	28	8 | ||||
| 976dfb78-9a3d-473c-aa10-ce6fe6580199	48	0 | ||||
| 2adc5316-a1f1-4d32-b99d-304740bf7faf	152	0 | ||||
| 31fd8f16-7f1d-4ba5-b151-3933cd74fb82	152	0 | ||||
| fac183cc-7002-49d4-a98c-61cef9fa1814	126	108 | ||||
| c25461c2-38a3-432c-aac8-dc2034c25b28	21	0 | ||||
| 04a557ce-1d5a-4de8-a570-9a1fc3bee89b	37	104 | ||||
| 2b54683d-1dd6-4608-85b3-d0fe388f2b17	150	0 | ||||
| 07b41846-99cb-4618-a005-0a64511270d3	90	104 | ||||
| 4faa192a-57da-4f39-8989-82e9a2b07c08	100	480 | ||||
| eb9cfc7c-9858-4639-b51b-d901a5bbfbbc	21	0 | ||||
| c2de3376-a7d3-45f7-b493-9ec028cbeda5	21	0 | ||||
| dd50bd8a-6048-4a74-ac89-c01238e6c2f7	20	0 | ||||
| dbeb85c0-2e85-4298-8c58-18686c7ee5da	21	0 | ||||
| a06997b1-bd19-4cc8-beff-62177f8f5e5a	32	104 | ||||
| f40c94b6-f460-404b-af43-c430a42357f5	272	0 | ||||
| 8b6d7bc2-a383-4524-b39b-5d0a28669e05	36	14 | ||||
| c56d616f-336d-41d4-9271-3dccf375675d	101	104 | ||||
| beb785dc-fabd-460a-9728-00f880fcf43b	930	103 | ||||
| 979fbd9b-ae0a-4bde-bcf7-2ea20976f5c2	48	0 | ||||
| f766f22f-d058-4dd4-b995-456962bea362	272	0 | ||||
| 8e824926-9817-4422-9fd8-62ccba23309b	36	0 | ||||
| 8cbd96cc-e999-464b-aa99-78f327b67c6d	36	14 | ||||
| dfc14a4a-e4ac-4542-b7b3-d9900fb3d18b	20	0 | ||||
| 17263397-77bb-4b90-8fa3-3457301c0997	22	0 | ||||
| 1767704d-ab52-446c-9699-a8c69352b550	22	0 | ||||
| 00f7f75b-b6d1-499b-97dc-92ef5beee40a	271	0 | ||||
| 8f0e552f-8c88-49f3-8409-37d0fa544048	36	0 | ||||
| 149d5633-a43c-4004-8b55-efe5822ade71	271	0 | ||||
| 1b59bdd8-3fc6-4956-a7d4-70a453a45b3c	271	0 | ||||
| e53a2eab-1e31-448d-907b-3656ca4e86c1	11640	0 | ||||
| 423b7a6a-c690-43e2-af07-ce65d409f821	22	0 | ||||
| 09b356bf-03e7-41b4-b338-6a8f50abb0eb	303	478 | ||||
| e0ada462-fa0a-4702-b9f8-bf2d5b32cb16	20	0 | ||||
| 4d2aa64d-98e4-4187-b342-507af0e0d441	10996	0 | ||||
| 551a691b-61a2-488a-910e-a6911f6b2372	730	107 | ||||
| dc3fdd43-763f-4509-b9af-3fa55e74a24e	21	0 | ||||
| 336e416e-b14b-42e2-aedb-e4d6baffed3d	119	107 | ||||
| 9830e938-25cd-44d5-b1e8-4c2ffe2a5631	48	0 | ||||
| 5c3cc6f9-5196-4cf2-a449-4e2aba5f7a72	50	107 | ||||
| b4ced40a-45ed-47d2-9069-0839584a09d8	887	107 | ||||
| c85d1ce9-a4ff-4bd9-ae88-3f150bb8e6ab	2111	107 | ||||
| 9495aa8b-528b-4f01-af9c-4f49e44d9b68	540	476 | ||||
| 8d52784d-ee5a-4d2c-8f4f-c9e796002ba9	10485	0 | ||||
| 99828091-801b-475e-b2ce-140f2cae6e23	48	0 | ||||
| 9c622801-1634-4bfa-acb6-2ec7fb706de0	48	0 | ||||
| 72d5d4ed-b840-4990-8b6b-e338df2d4192	60	0 | ||||
| 1725bfc8-e6a2-4e10-93f2-f0b7130d2664	23	0 | ||||
| 387b01fc-83f8-4cd8-a0ed-bda8aaf28f7a	911	472 | ||||
| 40918d3b-65fa-4df0-b84e-1e23a0df0c59	9640	0 | ||||
| 6d8fa194-49d9-4f08-9b0b-dfb6d6c49c03	24	165 | ||||
| ee8e3a6f-dd01-4e68-877a-33b04914f779	46	107 | ||||
| 6ad9918f-4360-444e-b18e-ea2cec4454c8	47	0 | ||||
| b03bbb1e-e5ab-461a-952a-94a27bc37dc5	256	164 | ||||
| eb749418-1fa0-458c-938c-44d739a90f9e	20	0 | ||||
| 03be08e6-dee2-4700-a856-d38778c777f8	107	106 | ||||
| cb61bbcb-1e53-4538-ba12-52a85d0763de	9381	0 | ||||
| eba158d7-0904-47f5-94d0-8e1292c25586	20	0 | ||||
| 7d45f9b3-3db9-4a58-85db-3b61ff3b4b22	60	484 | ||||
| 64b2b5eb-8544-4e58-955c-97b7cbe2314d	159	0 | ||||
| 176563cf-14d4-48f1-9641-898700e4b4bb	23	0 | ||||
| 1f03daf5-1006-4d80-b336-8196b1249b37	23	0 | ||||
| 1ff7d1ae-ff8f-43e5-b960-17c30645991d	23	0 | ||||
| 20647378-1355-4b79-986f-af32b36272b6	23	0 | ||||
| dea2a613-1418-4d69-8048-5e20626f1c56	21	0 | ||||
| 1a9333f7-5643-4914-9166-7cdf94b5ae8e	9356	0 | ||||
| 7368ab13-ca09-4000-baa4-bd6d9fa869eb	60	0 | ||||
| 5ca5a00f-d33f-45a5-b722-3bd2fab40fc3	22	0 | ||||
| dea48314-9a57-4bd7-80b2-170dfd55aa3e	21	0 | ||||
| 668e8301-49fb-415c-b89c-f87347bb8660	22	0 | ||||
| 171ef40a-5644-4543-a5cd-8025fdbffe08	21	0 | ||||
| 66ad8ca9-6ac8-4404-b085-b548d0e04a67	21	0 | ||||
| 66de576f-256d-4289-8013-c1d43e195727	21	0 | ||||
| 6760a4e1-bf9e-4e54-aea1-c3bf9bfda829	21	0 | ||||
| 678f0197-0262-4a72-ae27-c4ed73158420	21	0 | ||||
| df87ff33-39ec-4bcc-b4d3-f3449bd04ff1	21	0 | ||||
| dfb812ba-c4f7-4711-a1c8-cf99d5555b4a	21	0 | ||||
| b6022981-e988-4a50-b605-ab9f10cd32b2	84	164 | ||||
| e0a61160-30a2-47e7-bf3a-4e4af1205c8f	21	0 | ||||
| 1e9250dc-5917-476c-aac6-ff3092ad104d	145	163 | ||||
| 21eca204-3396-4ccd-b4b6-9e5b87c4e3a6	130	163 | ||||
| 7427d4c4-59dc-4de3-8fbd-60c659d685e1	60	0 | ||||
| 7618c96d-77af-4bee-ac1e-ae073e8fce36	663	106 | ||||
| 9dc15ade-7f6b-4fab-9216-0042d343983c	93	106 | ||||
| d027230d-c658-46ee-bad2-96e2f1f8c852	112	106 | ||||
| 8f112009-5608-402d-bc28-84cc211a0347	36	0 | ||||
| 8f2b2957-42b2-4a63-b6a1-5d66e63f97b7	36	0 | ||||
| 208107c5-2872-425c-b535-04daf324811c	23	0 | ||||
| 20ab614f-f3e9-4f31-be71-456cd76b090d	23	0 | ||||
| 20b2223b-fb0d-4211-be34-50f1ee61a010	23	0 | ||||
| d6728884-c1ac-4ea4-9e84-4b8b4f8bda16	122	106 | ||||
| f17e3015-400b-4e86-a96b-d572494d9d83	101	106 | ||||
| f9a8789b-de85-4f6c-a960-74cbacab20c8	34	106 | ||||
| 1756a2d8-dda0-48ab-bf54-0872c0858407	796	105 | ||||
| 9177ac0a-94ad-4283-ad0d-9e068e981893	936	100 | ||||
| 451a7398-accc-4a66-b516-9b7101caa8b3	23	17 | ||||
| 01b763a8-6c81-4f93-a684-e83580365045	782	448 | ||||
| 89da6f9e-b562-49dd-ba3e-d14da1994441	114	103 | ||||
| 581bf8c4-1137-415e-943e-d15d667fdc06	21	0 | ||||
| 9d5cea16-6342-47c6-ba9a-50002c1aa358	35	0 | ||||
| a261fe89-67a3-4222-b095-db45f4304c9f	35	0 | ||||
| 79b8ee1b-b7cc-40dc-a773-e4d5ef21e176	60	0 | ||||
| 03537c42-59bd-44b8-9aff-c16b884bb75b	20	16 | ||||
| 7412cf4f-e2e3-4410-ae69-976d82fe3af8	8733	0 | ||||
| c60caacd-4022-4224-8c14-cda1fef5b36c	8514	0 | ||||
| 9453970d-863a-45c9-9b33-d56383fea749	122	103 | ||||
| 95848fd8-4498-4bc8-979b-545058db5971	138	103 | ||||
| 9ff56907-3bd5-4f1c-9dec-58be0323c641	34	0 | ||||
| 62bb9be7-c70f-4930-bc14-9a12deafdfe1	23	15 | ||||
| 037b7851-bb11-4cec-b32b-e98466301a51	20	14 | ||||
| 30614b26-7f5b-42f7-be64-a89b544929c7	130	448 | ||||
| 78782d63-4ad0-4f22-a2c8-de94528df0dc	59	0 | ||||
| ec091486-c315-46f1-aff1-840cf857acf0	8376	0 | ||||
| ec32de90-c451-427a-af0e-ee2dd4a88d3b	21	8 | ||||
| cb09f770-49c9-45ff-b2ac-0ed237fb1d57	99	103 | ||||
| be93ad69-4a1b-4ef3-9749-b35ae25ed448	48	0 | ||||
| c0d4cd82-a9bb-4051-80ae-ddb34bd83594	48	0 | ||||
| dee1a6ce-2b0f-4661-8125-0a9bd4284d7e	29	103 | ||||
| 42d9bbfc-f93a-493a-81b5-cecc6599ac5f	23	0 | ||||
| 02ccab8e-1ee2-4d63-a54e-6659bec867e8	20	0 | ||||
| 516972f8-b236-465c-b3f8-29d5aa73e041	8205	0 | ||||
| 43970018-06fd-45c1-bf93-91ba5414eec5	23	0 | ||||
| e1a20b8d-f002-4879-95bb-a023d5f7bb97	435	448 | ||||
| 43ac82a9-9ff1-4312-8240-f1c31a1bafa3	23	0 | ||||
| 243ec3f3-9399-4a90-946c-7cf09e73e182	151	3 | ||||
| a4330062-076f-45d6-b475-70a991d7f6a1	6850	0 | ||||
| 9c4a2dfa-0ca3-4e69-8c04-701ad115a8d2	271	0 | ||||
| 44abc402-e849-4152-bd92-9d5c61c73114	23	0 | ||||
| 9eb7f500-2b04-4235-b0ea-28dd2999d08f	271	0 | ||||
| 2ea52c46-a231-40b1-81f8-3b82670ecedf	355	447 | ||||
| 9df3ba3e-f94d-4d30-924e-e549a66f6372	36	0 | ||||
| 032d103f-2f31-4c71-85a3-daa421d5b434	20	0 | ||||
| 9fa43c24-1a51-43c1-8c79-13920b3429a1	271	0 | ||||
| a0dd671b-14f6-4818-b4d1-2dd1feae67de	271	0 | ||||
| 9e2cd8a0-22c0-406c-be5b-5c25733b1abc	36	0 | ||||
| 6a588cd4-646b-4709-bb26-516ea180f59f	6773	0 | ||||
| 29d69597-5870-4c41-a440-6c856919a907	153	0 | ||||
| 9ebcd5a2-c0c9-4ff8-8617-6490573f7172	36	0 | ||||
| 78f7d9e9-4ed8-4d28-b834-973da35363a1	513	435 | ||||
| 81d128bb-b984-4323-9dcb-53ebeaea9285	6249	0 | ||||
| 2815f1af-7395-4ac7-a357-824b4cc843e2	41	102 | ||||
| 90e27924-ac10-444e-a276-33b4f99d02b4	152	0 | ||||
| 03794161-83eb-4ebf-b621-52c92dbdd06e	20	0 | ||||
| 482dac0e-04ea-4d69-b6cd-e0a6d56393f3	23	0 | ||||
| 969ca2a4-724f-44e3-abe6-cde850b8924c	152	0 | ||||
| 351adb1d-d76d-4180-ae08-e7286590fd47	3923	0 | ||||
| 96c0e2f9-ee33-4029-9db4-001ee5073df8	152	0 | ||||
| 4845527e-baf5-460f-9239-b6250ea14e83	23	0 | ||||
| b2aa40b5-df0b-44d5-bc99-ad3debe15455	97	105 | ||||
| a644cc8a-5518-4bee-9e9a-63c259d1c466	152	0 | ||||
| aadbdc6d-9cfb-4f9e-8956-7ed6a1e41ac6	152	0 | ||||
| 15b0d58e-4d42-413e-82e9-576a45c6f0e0	151	0 | ||||
| 50009828-ad3c-498d-acf5-a20e230fe240	81	102 | ||||
| 042a5e78-2823-4e1e-a7e5-e2b97b35284d	20	0 | ||||
| f5f7e6e6-17f5-405d-9509-33d7921a3503	35	105 | ||||
| c77ee698-fbea-402b-b9b5-6be6bbbb32f5	37	104 | ||||
| ec934a80-d0a4-4212-8c33-927cd48402d9	20	0 | ||||
| 54bedb39-fb71-4019-8467-cc08935705d8	23	0 | ||||
| 57e4cf5d-02f8-40bf-b7ee-06f5ada5e798	23	0 | ||||
| 583faa4a-612c-4258-846c-5ac307d9ed68	23	0 | ||||
| 595da9c2-fe0a-4bad-b754-792184073532	14	102 | ||||
| a1d18e99-c782-40bb-bfe0-4d1aa8f4fe86	36	0 | ||||
| a45b6cc5-265d-4a8b-aa26-d93fa812858e	149	102 | ||||
| 18855f6b-fb43-486e-824a-fa3c66f4caa5	150	0 | ||||
| ad60933a-73cf-4f6c-8599-beba974efce3	693	102 | ||||
| ec98ee2a-db19-4810-8b7f-4b1928d87ef4	20	0 | ||||
| 1c42b61f-6ee6-4457-a627-f9a3769b3e3a	861	101 | ||||
| 78202ae4-da22-49e4-a15c-a5d6b02697f6	60	0 | ||||
| 785c4e09-e742-4800-8d20-8d7b5b3d8a3a	60	0 | ||||
| a31f10f9-e493-400b-ac06-38c5a4a2d8d0	36	0 | ||||
| 2cfd4248-ecd3-40ac-a08b-69fa1d886f59	150	0 | ||||
| c8a2c306-86f9-471b-b476-2512f4d13627	38	104 | ||||
| 2b6a39e5-bcfd-45f9-ab3f-46d9b09a2c49	30	101 | ||||
| a34c5e0e-35b3-40a5-afd3-b7f7c149153f	36	0 | ||||
| eca74603-615a-4e1a-9616-18daa763d61c	20	0 | ||||
| 34d05b2c-e2ef-4a6f-b08a-f9ad4faa0a87	103	101 | ||||
| cba7fb71-9709-47f2-84fb-35d1c3e27e80	122	104 | ||||
| ed874edb-3b82-45c6-9729-13fc3bdc3246	18	0 | ||||
| 43145bd3-6d8a-4a9a-bee5-9095a2a00d7c	22	0 | ||||
| 43413a21-ad21-47f3-a9ee-a3c30cbed3a1	22	0 | ||||
| de8f7105-e643-4038-9fcc-197e05324638	143	104 | ||||
| 43df80be-ad8c-4f83-b8f4-80ec76591bda	22	0 | ||||
| 22943e07-9f96-47dd-96ff-8e4bc5ec8016	413	465 | ||||
| 3f85119a-4790-4a66-9176-a66d26c6c6cf	831	464 | ||||
| 35e825d5-0e95-4574-90c7-2b28a49c4284	109	101 | ||||
| e423397f-0bf4-49d8-a3c8-0aecbd5be9fd	625	104 | ||||
| 6ff423b9-c2a3-44f7-b33b-bcd284bd19b3	478	464 | ||||
| beddf86b-bad2-4662-8111-bf7507009a89	153	452 | ||||
| d0abb4ce-1a0e-4b07-b7ef-8d114baca29b	866	450 | ||||
| 51f5da8a-30ab-4903-a3ed-b9dcb8feb3ac	22	101 | ||||
| 556c4e98-7e6b-43ac-aa67-1e0b5de8f62a	122	101 | ||||
| 7060b8eb-71e7-43a7-a1ec-c78cb3975aab	115	101 | ||||
| 785fc0c8-771c-431c-8149-51bf112430aa	60	0 | ||||
| 100ad959-fb82-499f-844c-520b283ff197	148	0 | ||||
| 786547ef-572b-4089-9b58-d67cb515518f	60	0 | ||||
| e49f3ff4-f7d9-4c4a-92d1-7b37efc36956	108	449 | ||||
| a383ffe9-a07d-4d23-a78e-1f721143f9cf	36	0 | ||||
| a3ca562e-bbb0-42dd-a525-bfc28cba5ec0	36	0 | ||||
| ebb8be73-e23d-41db-9113-2d3673cdef10	469	449 | ||||
| 78a9b4d1-266b-4f05-b5ba-89f6c0f61859	60	0 | ||||
| ed96c5fa-9e3e-4370-919a-e550e5c20030	123	449 | ||||
| 729db178-fc6b-4486-90c2-cfab05c616b0	129	101 | ||||
| 7b7ee26b-e4a3-4892-9b74-c60b8729112d	96	101 | ||||
| 5a0db640-75fd-441c-8ccc-50b47b4f975f	1368	99 | ||||
| e037a86a-b782-4b46-b5ef-129c2d6f92f5	139	102 | ||||
| 5f330cc5-ff26-40dc-8f1c-b451b0f968d7	94	99 | ||||
| 870a88a5-20c9-4aa7-9983-809586135281	59	0 | ||||
| 7ae23d05-bdc5-45f3-888f-c225be7dc4cb	23	0 | ||||
| af3a1f39-b479-4093-8600-eeaf63727904	36	0 | ||||
| 9ce1e678-9bfa-4a18-a472-3641b5361cb6	40	101 | ||||
| 87c21063-8c53-4642-b08c-525e809362e5	58	0 | ||||
| a5a3493f-ffe9-4575-896e-f9e35e4e5f02	118	101 | ||||
| 87d78454-c350-40af-bd1f-4da5df0f664e	58	0 | ||||
| eb61ec1b-693c-4ca9-b5d4-e18a579089ba	48	0 | ||||
| aa5c90a8-095f-4d8c-bf6d-50254c8b77f7	43	101 | ||||
| 46d8f4d5-2cb2-4066-ac5f-8b99edc8914a	113	22 | ||||
| 0d8c40a2-d22a-4ff1-bd11-5a0e63a13fad	18	0 | ||||
| c404b33f-37f3-4ce8-8068-842c6672eb82	5380	0 | ||||
| 7bd74829-f479-49be-823e-2c12dead41eb	23	0 | ||||
| afc1e03a-cac6-47fe-b793-65d0eb9aee59	46	101 | ||||
| 5779a03c-af83-4389-8cbb-2812942b02c7	35	100 | ||||
| 66e5f965-3d28-4dfa-b86d-b321c7f12959	23	16 | ||||
| a220559d-98cb-4592-9d29-ae4b9d36d15d	5203	0 | ||||
| af46b3ec-11d5-4f25-a77d-a12290d6587d	36	0 | ||||
| e0c9b7a8-bc6f-4a92-98f2-f626a09ebde5	47	0 | ||||
| c179e8d5-7bd2-45d7-99df-618a365f0149	4639	0 | ||||
| 62ce11c2-5a77-4762-9f16-b654556d8148	22	0 | ||||
| 671e90a3-f913-4e36-b7ed-cd361cb18e59	22	0 | ||||
| aeaca89f-7213-46e3-b5a1-03c9f08fc50e	991	439 | ||||
| d4c2b444-148b-46f3-8181-f0f95beea71c	41	101 | ||||
| 08f31ba5-58d4-42cf-b684-6ec234c45c83	272	0 | ||||
| f221e2c2-d326-4150-8fa4-6aa1ff3999cc	92	101 | ||||
| 0d408d7e-e902-42d0-9499-fad9cf53a2b4	20	0 | ||||
| 5a2709cb-6dd5-4e28-b3ba-4ee62ace084e	3610	0 | ||||
| 841067cf-a457-4670-8b0d-86a2b1fac9de	60	0 | ||||
| 684a6358-5971-4206-a36d-26a53de50765	23	22 | ||||
| 2d9aeff5-686a-406a-b6ff-492282799b1f	573	437 | ||||
| 175160df-29bf-42b3-a4d8-55d14df85105	1701	0 | ||||
| f13c62d1-aa6c-4cc1-a380-8f6255da5a51	1688	0 | ||||
| 668281c5-8256-404a-abdf-c24656ea9ed0	108	100 | ||||
| e95e5359-d9cb-4a6b-a8b5-f41f1e70316e	50	0 | ||||
| e2329717-5503-4844-bcce-7ff4568edcdd	40	0 | ||||
| c5d3dd61-b77d-48f6-97d8-294ce2705205	48	0 | ||||
| b88318b8-ef8e-47a2-880a-ea05c4231819	1627	0 | ||||
| 41ccb381-6c74-41be-b980-5ee8f6add15d	1620	0 | ||||
| 05d6a9f4-d920-4e11-9f37-185f00434a81	19	13 | ||||
| 989161b4-a638-40c7-9a2f-08729fc31ac0	3591	0 | ||||
| e0e7bb1b-8a72-4230-a129-78c3e1d3fc23	48	0 | ||||
| 8818451d-0b1f-4138-8f74-047d23d63514	61	22 | ||||
| acc1ee3b-28db-4bfd-9c75-58e2b8963e07	36	0 | ||||
| 7bfb4dbf-5614-4bfb-b275-2b141e5fd280	22	0 | ||||
| 7c844c1f-0cb2-4606-b6de-51d5463f75ca	22	0 | ||||
| fb3f7b71-d0ab-438d-80dc-733fc4d8b821	36	101 | ||||
| 44f70e70-1e46-4def-812d-fd3d63e5bed4	34	100 | ||||
| e0e8262b-44c0-4385-9de5-dba15e41f835	48	0 | ||||
| 66997b3b-3107-4d09-a90c-8712477fbb58	21	20 | ||||
| 67442c8c-3d07-465e-89ae-406c1ccc87a1	23	0 | ||||
| 7c3eaa29-bc6d-4fca-ae0c-ea6adc9f3674	114	100 | ||||
| 67654d19-2756-4118-b86f-5d48a8ffbe75	23	0 | ||||
| 8031f0bb-c103-496b-bedb-b08d46f8cfd9	129	100 | ||||
| 3c2ed0dc-ab73-4ccf-ab4e-6a4e81fe83eb	151	13 | ||||
| 679fa328-a826-4f75-8c80-3ab091751f85	23	0 | ||||
| 86e12c45-8ea0-4db8-b7c5-1c575a3b1f91	60	0 | ||||
| 870216f3-56ef-4a9c-b6dc-94a56d9565e1	60	0 | ||||
| 8703ca7d-f06c-4265-b59b-0d1b5fcede14	60	0 | ||||
| 0e1fd2c9-7c44-4d23-b492-ef1ab1b2b068	20	12 | ||||
| 67deebe7-74c5-4b97-a430-770b3732b513	26	20 | ||||
| b345055f-45f1-440b-aed9-0d9c43992e82	902	439 | ||||
| 6be1f2a3-885f-4cc1-990f-305a3219c769	628	437 | ||||
| 33126c81-8203-49ff-ba4f-47e4b8de8279	3562	0 | ||||
| adf32acd-aa60-4c2a-908d-bff90bd95206	36	18 | ||||
| 8c2aad9b-2eef-43e2-a45e-15e10b160adb	108	100 | ||||
| 0e24d0e5-01cc-41b0-954d-a06193f9c082	20	0 | ||||
| 67fdf6ce-d106-493a-83cc-a4d6938025b4	23	0 | ||||
| bff64936-8dda-4909-b645-aaec66131b9d	806	439 | ||||
| 87988c94-9600-46b7-b222-07590ac9e62b	60	0 | ||||
| 0e812aeb-c5a0-4e8a-8875-31298460b162	20	0 | ||||
| 32e229bb-90f5-416c-be45-3ced6450aab6	270	0 | ||||
| bf4fd348-e6d4-4012-8333-5069496155a4	651	437 | ||||
| 0cbe5b09-53f7-4531-9a27-717393d334fb	20	0 | ||||
| e829b80e-a713-4a4d-96b4-db244f88c3ab	48	0 | ||||
| 6b5e2bd0-ebf9-4007-a3eb-5f104a853877	3542	0 | ||||
| cd8f33da-d579-410f-9f9c-27859bf49d85	570	100 | ||||
| 3c6af6be-f937-46a3-b77b-f11103bb9043	151	17 | ||||
| acfa1e74-5570-47d8-afb3-ff3974f67581	36	0 | ||||
| ad0459bd-ce59-48f5-9eb4-36ace78c242e	36	0 | ||||
| ad694528-5a2f-43c8-98bc-e0235f7c4644	36	0 | ||||
| ad837942-ee3c-43fa-9af7-79a205ceddef	36	0 | ||||
| ac53dd1f-0576-4f74-8684-2490ca610957	35	0 | ||||
| 7b252429-4c78-4895-b93c-9cd4a3a34e04	23	12 | ||||
| cef4ceb9-190d-4f96-8bdc-4ad8322db438	713	100 | ||||
| af044c19-3edf-480e-b00f-dfb888dfdfed	36	0 | ||||
| c5f010f9-7c15-4265-83d7-846b7d9ee210	120	439 | ||||
| cbca3c30-4ced-41ec-8991-27f1914e52b1	120	439 | ||||
| f80e43df-3dbb-4034-a74e-9242231609c8	2050	0 | ||||
| d7daba67-471d-4f96-a1dd-acaf651b0040	148	100 | ||||
| 0da94bbe-cfca-4670-add8-169e67f3ca92	19	11 | ||||
| ad236989-67f5-4f0d-8425-c64bd942cd42	35	0 | ||||
| b0b19338-b80b-4bda-94b5-722b21403e44	35	0 | ||||
| 68435ca6-34ed-489e-8a4d-9beb54720257	23	10 | ||||
| 0e8b6641-0a7a-4174-86a7-ed08830a0310	20	5 | ||||
| b01962df-52d1-476b-9ba2-d26cc9f13945	34	0 | ||||
| b042919f-8167-472e-884c-658f6ce103f4	34	0 | ||||
| 0f0ccbc9-c0a4-41ca-b2c3-5fc3fe00db35	20	4 | ||||
| 686b3ad5-0815-41f2-bf75-6486c43112cc	23	0 | ||||
| 68a128a2-04c1-4742-a3b5-4b596aad3fe0	23	0 | ||||
| 0e9bee77-d2e5-4415-abc2-40481eea14af	20	0 | ||||
| f6ee0bc7-e975-4f8b-a869-7332a31fe9a6	943	439 | ||||
| 363b87e8-6029-42a5-becd-75c2354ef7fb	151	0 | ||||
| 0f0b6276-783d-4701-9489-647fb2246a64	20	0 | ||||
| 0f125327-3c86-4941-bda7-e4b5783dadfa	20	0 | ||||
| 1d9de7f5-ce21-474f-8499-e23ab8f4ce7c	467	438 | ||||
| 4f94f745-7342-4d9d-8d7e-88c1ff44f86e	563	436 | ||||
| 33e97348-fd37-4602-94bd-d9d91083f195	150	0 | ||||
| 7bdc72fe-dc2a-4670-8ec5-1e57c0c83451	742	436 | ||||
| 8801a829-4752-41fd-b3e2-c9b6963011d0	60	0 | ||||
| 882f9d8d-bee1-44c1-ada9-062044414a51	60	0 | ||||
| e83aee5b-e722-47bc-8672-163cb1a7ab38	48	0 | ||||
| b0252cc2-d1a8-4149-b153-632c8ca38c7f	36	0 | ||||
| b07cd452-1eb2-4058-abe4-b3c2dfd1a2ab	36	0 | ||||
| b0f9f4cf-ec60-449f-a2c8-a763414f9252	36	0 | ||||
| b3c70b7a-fa54-4aa2-909a-96ae3e9b8318	55	436 | ||||
| 126d7086-315f-4961-a099-7b7414585f63	2011	432 | ||||
| a298b29a-3896-4e35-90f1-d0f0ab664656	63	0 | ||||
| a9b967ea-312b-4bef-9d2b-73e98907e5c8	1273	0 | ||||
| ff193162-53bb-4d15-8db3-825efedbea55	39	100 | ||||
| 25e4a4da-0c5e-4be5-9993-d4120a2ba29f	4613	0 | ||||
| 84edbbc2-ed75-4c82-9414-44312e42ca3a	22	0 | ||||
| db7ca8d8-73db-40cd-8b9d-8364aea6f952	4512	0 | ||||
| 30e51c45-a29d-40bb-a232-1078bcaf7974	96	96 | ||||
| 4a6102b5-dce6-45a0-9ad8-868ecd984b97	1355	0 | ||||
| 19c40e1b-52a4-488c-a813-aa12f28f184d	20	0 | ||||
| d78c4d55-193a-48bb-b726-077ad0ddc627	107	432 | ||||
| a02ad929-dd06-4bcf-ba51-9df34b1d4ff6	60	24 | ||||
| 1ac49a9f-a3b2-42b6-9ad1-155632ac61c6	20	22 | ||||
| 0637167f-6824-492b-b8f9-9a1f10430f6f	115	21 | ||||
| 21e2d1a2-4af8-4f3a-8315-a20db534b725	20	0 | ||||
| 84ec8b0e-17d7-4182-92d5-c9ec0e325a91	23	0 | ||||
| 19e7d326-5a64-457a-b946-6e65ba6d0c73	20	0 | ||||
| 015b1c31-7ba5-4b41-9a12-c3339f3849a1	1299	0 | ||||
| b2eb725a-e83c-4afb-95c7-f39a97d06fa9	36	20 | ||||
| 1a21765d-e704-46dd-8427-c3e0c40a8848	20	0 | ||||
| 8683e69f-cc70-4832-b3df-3c9f34f959df	22	0 | ||||
| 87cefbf6-72db-4b21-8fcd-c9571b385076	23	19 | ||||
| a2e3d17f-bc42-404d-9c14-4e2645496450	60	0 | ||||
| 870bd8c1-ed5e-46fb-ae30-4bc0060faff9	23	12 | ||||
| b165ea5f-23b0-4dc8-8fda-8db1e34d19af	36	0 | ||||
| 6fef266d-864e-42e2-9b2b-a82af8f06f07	150	427 | ||||
| b47d678a-4c2b-4530-a5c9-4e671ebc3d93	36	0 | ||||
| d349881d-911b-45ed-b82e-ff38b8853076	163	415 | ||||
| 1326b307-6307-4e17-9b0f-bebc4b6195ac	1275	0 | ||||
| 29c2a7ca-7d55-478f-99c2-5a65955bba41	146	413 | ||||
| 190c6eb2-1114-42c8-8249-69bf029db7ae	20	17 | ||||
| 1962c06c-d522-4014-92c0-964c7bea8b42	20	9 | ||||
| 1957180e-b11f-4dcc-82a6-48bbb02c65b3	20	16 | ||||
| 1951e01e-f5d3-422d-9c66-cfcd03a11316	19	0 | ||||
| d2534dbc-9fe0-423d-afe6-d2a38ae307c8	567	423 | ||||
| 8785a83c-137b-4fb9-a4e5-ce33410beec1	22	0 | ||||
| b1edb8eb-d1e0-4881-b485-654ca1a42466	150	8 | ||||
| a204e344-b253-4f0b-a457-d167765a88f7	52	0 | ||||
| 67ef5438-a467-4e4a-8574-70be5826952b	116	99 | ||||
| 74ba7e9b-e3df-4ede-8082-deb0e6a732c3	135	99 | ||||
| b293e4c6-e20a-4050-9504-25dc97ec9401	35	0 | ||||
| 7dcf1710-45f5-48ec-9a9c-bc14ae6d930a	1421	99 | ||||
| 19a9f5b5-ea6e-4770-86f4-d82a6f8d9bfc	19	0 | ||||
| 84fd1f0c-8ec9-460f-81d0-39e272a11da8	23	0 | ||||
| 691a4f5f-339c-45c7-8eb3-fe3fb51dbc21	1619	0 | ||||
| a17c0e5a-3e07-4c87-98cf-4209e859dee9	151	0 | ||||
| 2bb30f59-0cc0-4103-b9cf-49a7d20bc2ed	195	413 | ||||
| 86b1d0d3-f175-4cd7-83b3-ce82f636c2cb	23	0 | ||||
| 1d2762df-007e-407b-b57c-94e0d2669449	19	15 | ||||
| 11ec7bda-8787-4e88-9674-89cce182f2fc	20	0 | ||||
| 211662ce-584b-4967-a061-c000c4f5b572	20	12 | ||||
| 8fc7715d-2028-4b38-83bf-6554086c2f55	1385	0 | ||||
| e4fe3574-4fee-4483-aeb5-c1280d4a3930	359	413 | ||||
| 133bd6da-7290-4fcc-9036-bff03d3b398f	1383	0 | ||||
| 99aa6606-c519-4e40-965f-2fc327a65749	1378	0 | ||||
| 37d1c66a-6ea0-458e-a8e3-7b90c58eb2b4	1359	0 | ||||
| f7ca960e-988a-493b-8444-23c2010dd378	1358	0 | ||||
| 213e9145-c764-47f2-a0ab-c28e54169457	23	1 | ||||
| a004917e-f262-4169-97d9-75d6766e2a1b	60	0 | ||||
| fd44c06f-c8c4-4cc5-9ee0-f3b0c66b3641	1358	0 | ||||
| 0cd49d01-2137-4a51-9183-47ebf6df1eea	46	411 | ||||
| 85cc419f-7322-4448-b6d0-1bab0e4cff23	28	99 | ||||
| b394f451-9ea9-4368-8839-f7e844e4b610	35	0 | ||||
| 9e2bec3e-8899-4759-9e03-2f84db61e7b9	36	99 | ||||
| cf0d2a88-43a2-4580-8d08-acfd1ae1db2c	28	411 | ||||
| c8cb4004-43b3-460d-9e3a-180832dd3e80	38	99 | ||||
| 34f1d417-1bd1-4bf4-ba4c-b119f7c8d0db	269	0 | ||||
| b20bd200-b17c-4ec9-8643-b2f3b17b80ee	36	0 | ||||
| b4c3cbad-fa07-49f5-bbc6-974fbf8fdd0b	36	0 | ||||
| a00cf6cd-4629-41cf-bc6d-564cfb665f11	60	0 | ||||
| b29f474a-b1a8-408f-ae0c-64f0b0213696	34	0 | ||||
| 872ee3ab-ffff-45b8-b171-fa2058eea33d	1095	434 | ||||
| c4fe19b9-b599-4422-952b-226a92396e26	391	422 | ||||
| 88c04adf-4cf9-4e29-ab0f-9110eb194e92	1063	432 | ||||
| 556ddfa2-1d09-4543-b19c-43e0d984bb45	1618	0 | ||||
| 86cea6cf-d9b5-48e0-a59a-15252ad2d253	23	0 | ||||
| cc58c7ec-5b11-4686-8573-034a019a7706	865	410 | ||||
| 86ed5177-4e77-4edf-810e-a1e69fc39276	23	0 | ||||
| 4549182d-c4e9-4bf1-805e-7db761e3e836	269	0 | ||||
| 967f2b4e-00a9-4739-b267-2d2010af7010	60	432 | ||||
| a10d56be-8597-4a28-94e8-fcf3ff10069f	60	0 | ||||
| cc82b0bd-153e-4c2e-9546-6b6386491a3f	664	410 | ||||
| 879e3cfc-185e-44b8-b5a7-1ec14d4cab3b	23	0 | ||||
| 6b6f882a-c704-4040-999d-931a85f0e66e	1605	0 | ||||
| 5fc0a5ba-999c-4637-b61d-082c903225e0	1599	0 | ||||
| 822f5a41-b9a7-4b20-a9be-a8652fd317ed	1593	0 | ||||
| daea4559-e3dd-41e4-b15f-9f1911cc685d	16	99 | ||||
| 02fd03e7-1bfe-47d9-8f73-97b1a94a8fad	120	98 | ||||
| 30019311-1675-49a6-b8a6-989aa822dace	1275	0 | ||||
| 3f55df5c-290c-4e85-a841-6ace0c403c23	29	98 | ||||
| b22668e8-6f3f-43a4-ba2b-c47b4d2467e1	36	0 | ||||
| b29ef876-896c-4e66-ac0f-79f0f638334e	36	0 | ||||
| b2ae034e-3a06-48dd-8af9-8453d39cd4d6	36	0 | ||||
| 193479e9-2fa1-4c29-99b5-37da8be4907e	20	0 | ||||
| 514a3fd2-b3e4-41b0-96a4-30d88c28adf3	110	98 | ||||
| 87066c49-5709-4418-ae2e-a5afc441625c	23	0 | ||||
| 99f7b0cc-6ae8-4b9b-af9c-f373da343f56	149	0 | ||||
| 0f1fe81d-9af8-47b8-9757-1c6c53de09e7	494	408 | ||||
| 87f7037c-d743-44ff-a65e-4e60acd484f6	23	0 | ||||
| 1276c6e2-44ce-4e91-819b-53741dcb5b77	161	407 | ||||
| 87f12b8b-92cc-43d9-bc93-37c0cc7d6000	106	98 | ||||
| 94d8271c-a6e6-46da-b76c-a67c427b0ad3	21	98 | ||||
| a1bf6b39-11cf-4963-a52f-c97b4eb5551a	60	0 | ||||
| c341d240-0839-4ab2-afcb-8bce8b9952da	112	98 | ||||
| b3ae8dd8-e384-4dd0-8b52-972d7e2519df	36	0 | ||||
| b3b7983f-d495-49f9-910d-a0c814675fd9	36	0 | ||||
| b3d1b58b-a9bd-46fb-abbf-fb79dbf0811b	36	0 | ||||
| 88661166-0db5-4fe2-a322-d65105ea62a3	23	0 | ||||
| 888bf3b2-f829-4938-b0e6-97d0e21659fe	23	0 | ||||
| 89091843-a159-4855-8dbf-8edbd13df395	23	0 | ||||
| d6455661-4550-4967-877e-9aa9ea99555d	88	98 | ||||
| e527af77-7fe3-4dfd-80a5-9f6de96173f6	140	98 | ||||
| f7162c3c-035c-4266-8504-5f05b361f1a8	94	98 | ||||
| 1d043145-4d44-447b-88ce-fb96f839b74a	33	97 | ||||
| 373242d4-e2d2-4d0f-947e-ac99570268ef	101	97 | ||||
| 3e8f24d7-4a3d-4963-bf7a-3eef47d2c96e	96	97 | ||||
| 4b962ab9-2903-4457-bd1e-4abf0b0ed4b4	126	97 | ||||
| 4ce67d92-efb5-4ae6-bc20-d1d700d0ac90	140	97 | ||||
| 946d8e74-848f-49a7-bb77-eaf786cc1f6f	150	97 | ||||
| c61adf2e-03a7-43bb-a3ac-31cb2b10eeab	108	97 | ||||
| b5b69f34-53aa-4b1e-8256-62576ec7d2c5	35	0 | ||||
| b6f0af75-6d29-44d3-8967-7d90831ab1b7	35	0 | ||||
| 03e06350-1904-4e69-a99a-d2177c4ff196	46	0 | ||||
| af360f36-c65e-48c3-899c-03828f4a2c19	431	409 | ||||
| 7cda54a5-fb68-4cc3-8684-a46ec88a6a22	146	96 | ||||
| a99f7cea-5b5f-4d56-a010-20d6580c2253	36	20 | ||||
| fb3f4b09-b8f6-4874-aabc-2fa2b14b77d3	188	408 | ||||
| 8feea3bc-44f9-45e5-bea5-9471ac78bc77	23	96 | ||||
| ab83e77e-2fc0-4522-baec-043e3dcbdc76	62	0 | ||||
| b7348d97-8b00-4580-9a78-bb7433a498fe	35	0 | ||||
| 1fd8779a-5b50-4a78-a828-fca4e28f6b67	1075	0 | ||||
| c454616b-db92-4902-af57-bc92bd31ec12	35	0 | ||||
| 58c7a06f-2485-46ed-b804-95c6f8a56f1e	1075	0 | ||||
| 7864a913-d482-45b3-b90d-d15be0119f78	92	394 | ||||
| c8c82563-52da-4547-98cf-7212fe580881	35	0 | ||||
| c180d0d0-bbbd-4c78-ab5b-c68917661d61	266	0 | ||||
| ac1b8e5d-1cfa-4dac-b1b4-99038cf7cff2	60	0 | ||||
| 6988d9eb-dca8-4bcd-bbaa-17bb7c08a537	1042	0 | ||||
| 1a9feed3-b3b1-41ee-9028-639430fdf084	43	0 | ||||
| c8cebc09-ee8a-4a0a-a410-c028871828a3	35	0 | ||||
| c9ae6be2-bf64-42ee-8ad1-ebcc38b3f7fd	35	0 | ||||
| 9189cc1d-bd21-458f-a491-5d98fcd743ab	127	96 | ||||
| b59a3b2e-2ab8-482b-b3de-8725d5962c88	34	0 | ||||
| ada42f04-e584-4e7c-a08f-ffce4d1f92f9	60	0 | ||||
| e57b45a9-809b-4222-a009-4517db2bc267	120	96 | ||||
| 00966752-fbc8-44a3-a4c6-bd0dc8c378bf	93	95 | ||||
| ae32bf20-0a09-48f4-98fb-0d4a994b812c	60	0 | ||||
| c99270f1-46db-4bbf-a8cf-5cddd89fb092	34	0 | ||||
| 2051c6da-d6d6-4ba9-9504-9cc4b516b11d	543	89 | ||||
| f3a88950-3f1b-4e89-8e3d-64e54f785e45	151	0 | ||||
| 89477009-4071-42bd-86bc-72cd713f8a52	23	0 | ||||
| c2a7f1b6-f2ec-4a02-8878-3c678860747f	39	24 | ||||
| add5f380-f257-4a74-8b8e-6523ca708516	52	0 | ||||
| fa906948-fefa-42d0-b205-c2e015731fe3	151	0 | ||||
| 69459952-e4d7-4695-9618-a06c835c7e8f	57	393 | ||||
| 01d64f82-8c01-4844-9d8c-1d8ca4ba70cc	150	0 | ||||
| b647f4a2-d330-4f4e-a612-ed98789f97d8	43	19 | ||||
| 0359923f-f240-40ab-9e45-b7bbed25ee27	150	0 | ||||
| 8990fe0d-a458-4e02-9ea9-184d878c9b9e	23	0 | ||||
| 08eb737b-d49c-475f-96ab-d91ffc5daebd	150	0 | ||||
| 15eb3671-34e9-4099-ae1c-ace48954b6b9	149	0 | ||||
| adaf0448-32b9-4843-b678-42cc428cb57c	60	16 | ||||
| 0899c67f-1e5c-416d-93fc-00f46b4d8c78	148	0 | ||||
| 04aef27f-e6e6-4400-8b38-e1ce3d230c69	49	0 | ||||
| 018e7e6e-ae60-49f6-921e-d9767d0d9338	47	0 | ||||
| 7049872b-786a-4ab2-b702-bdd362a06846	618	393 | ||||
| b260de77-9546-4f22-b4ac-7d6f030a61cc	23	14 | ||||
| bde9e42d-a81a-44d6-85e7-db41a59e02d4	34	12 | ||||
| bf47c417-c571-4ac6-ad05-fdea0357efb6	1272	12 | ||||
| 6271ec60-7f15-4d63-a559-5c98c4a67cf8	25	22 | ||||
| 832d081c-45ff-41ba-aaed-0d34d0f1c42c	20	393 | ||||
| bfd03b36-e33a-401d-a485-80134a4e62a0	23	0 | ||||
| 0d03143f-f04d-4aee-90dd-27bc95e5e4eb	47	0 | ||||
| 7cf1530e-c2fe-4bc2-8c33-1843fcf597ec	40	22 | ||||
| 7f8755be-d684-43be-84ce-4da7939eac59	38	22 | ||||
| 2dcab519-ae51-4c41-9eef-fec4061c60bb	4166	0 | ||||
| ab86493b-62a2-4621-a2d1-8efc74e7087e	36	0 | ||||
| c0324c41-c2c6-49c1-8a27-b6768749a811	23	0 | ||||
| ed42164b-f8db-4ec0-ba97-f45fb40196ef	4123	0 | ||||
| 18489caf-e08d-4710-9c28-cacd8711ef2e	47	0 | ||||
| 7e2d9e24-5a09-48c8-b32e-2767144b13bc	4079	0 | ||||
| 71945960-2bad-474c-a652-c004ba8f4887	3965	0 | ||||
| b59997b2-84ea-4605-992c-6861dee12ef9	36	0 | ||||
| 18a75de7-9599-4662-8af9-53c05623e9f1	47	0 | ||||
| 18c855cc-5862-4962-8157-c2e45779fb1d	47	0 | ||||
| 194cfefe-318a-4e79-a61d-e9c2136c1e29	47	0 | ||||
| aafafea1-e461-407a-8673-455de5b5eefd	30	21 | ||||
| 1a3d3071-60fe-4231-8dda-d9dfcb79db5e	47	0 | ||||
| c3d5d8fa-8986-4657-a516-a5e9a43e2c9a	23	0 | ||||
| b5b07d5b-091a-4f65-94f9-98161f09c18c	28	0 | ||||
| c3df86bf-4135-47aa-b528-f2bd0d3f9880	23	0 | ||||
| b5c14b3c-a81d-4bc4-b936-4af3adc2fc0c	36	0 | ||||
| c9772b3f-c7ad-4d19-bd93-fc16f0002206	27	0 | ||||
| b6edb93d-ad8a-4dc2-93cf-ec5318554ebe	36	0 | ||||
| c46afc39-c72a-4a74-9478-c6636dbb5e10	36	0 | ||||
| acb383e9-145e-4092-a16c-c95f19f0c988	46	21 | ||||
| c9a66973-e6b2-4776-bdb5-577b3d4c4caa	36	0 | ||||
| ccd4e0c9-347c-4d40-b40b-76ee422d7e4c	23	0 | ||||
| ca103457-8d67-43b4-a377-b41803a31fb2	36	0 | ||||
| ca4449ea-e4c8-4758-ae1d-ebc44b0cb2af	36	0 | ||||
| caf3ba22-4ae3-411b-8ee6-63463240b71a	36	0 | ||||
| a14a5f69-d9d1-4a09-8af8-5250016993a1	1260	0 | ||||
| ad02b3ce-8498-4d47-8f36-e2b1a58fde5d	30	21 | ||||
| add41aa7-b5cc-4bf6-bbdf-b64b84744af6	28	21 | ||||
| cce7a757-a60f-4aeb-893b-d9e69bfe8109	23	0 | ||||
| 31b09eb2-58fb-415f-8e23-a683a6ce57fe	1257	0 | ||||
| 2701e59b-6a80-49dd-8744-8c30c717ef64	1076	0 | ||||
| ce14df54-7d93-4bf5-bad2-dbbd3515257c	23	0 | ||||
| 7460614d-7665-4252-b09d-a9d33f96cd2f	1076	0 | ||||
| bcd32967-d347-4d54-b38f-8cca1b711445	269	0 | ||||
| 89235288-e887-49a4-b3f1-e4a36e1a2f19	23	0 | ||||
| cd9a3022-0a53-494b-948a-c33aaba47742	22	0 | ||||
| 25e5720a-fe7c-49d7-a606-cd6b57b68e4a	20	0 | ||||
| 264d5ebe-613d-450a-9fb9-dc318fc1722c	20	0 | ||||
| 56a76324-03f8-4edb-8790-abfa4cc4e739	47	20 | ||||
| e6119f6a-89d5-4ad4-8162-b259161bd430	466	410 | ||||
| bbf052d8-7921-4593-837f-f879851426e7	268	0 | ||||
| 73122c6e-9592-426c-95cf-e423eecef3ca	198	409 | ||||
| 9e8576ae-8082-4b9f-8b61-ddf39040f248	480	393 | ||||
| 70ff46cf-0887-47d5-b7f1-16801dca1c20	81	389 | ||||
| d80043a0-f391-46cc-8e85-fad19ae183b3	1076	0 | ||||
| df4fe2ce-376a-43c3-afee-e5cda0f6def9	268	0 | ||||
| 5727ffa1-95c6-4c11-807d-936cc05daec6	446	388 | ||||
| 6c5d3411-ca44-4973-b0ec-9eb1b5cb647f	495	387 | ||||
| 75b81c09-3919-4dd5-a2bc-b85af3ccb444	72	387 | ||||
| e2ff87a3-b938-494b-842f-2038f4b3c5cd	351	380 | ||||
| fcc5b9a8-c143-4eb9-8132-68c1731020d6	653	97 | ||||
| 054f51a0-8988-4ca7-b948-0663ff6a8706	30	96 | ||||
| 1a936e70-dbaa-4f2d-94e1-574070036e3a	98	96 | ||||
| 4313c8af-0f35-4af8-9736-f2567b16a658	108	96 | ||||
| 9df3bd29-ffe3-4b63-b432-3c382bf6dd65	42	20 | ||||
| 854f240f-b025-40e4-865f-1f15633ecda9	267	0 | ||||
| 902612d8-312e-4f34-bdcc-407fbe9228fa	801	0 | ||||
| 9e759c53-b07b-43ac-a74c-01351cd07816	605	332 | ||||
| 08b821bd-ae19-4aa7-aa23-0f625e90807c	800	0 | ||||
| e7b7267b-1ce2-4f3d-abcb-fac886a70676	222	332 | ||||
| 0fec6297-76ea-4438-ace6-8bdac16d48cd	57	0 | ||||
| 0f4cb67b-ee09-4580-a6f6-96a6c3f51053	738	0 | ||||
| f550e947-a139-44ca-9546-b5afe3b7e98e	592	332 | ||||
| e71b05c3-2265-4e18-aff7-84fd4a14bf62	53	331 | ||||
| cf1b90ac-5c85-4ed9-a30c-7ab5589ccd6c	36	13 | ||||
| d5b15b7a-cf05-4f9b-8abb-e754d02eb067	59	0 | ||||
| 991573f7-5020-4c3e-bb36-f42d09511e0f	267	0 | ||||
| 35d9aadf-dd21-48ec-b6d3-76121991bd06	19	0 | ||||
| cb771273-3d02-4be6-a63d-2fb300a9dcad	87	96 | ||||
| d5ac9ac6-49f3-4a39-a9e1-5057f81ad6b7	60	0 | ||||
| cd68aad4-6291-4575-a37d-ca9f0f5d169e	36	23 | ||||
| 91df1242-2954-444e-b888-888b6fa34586	938	0 | ||||
| 7f97e1c7-9a37-430f-b513-876aac2ac17e	108	95 | ||||
| aaade3a4-39ab-468d-858d-691f009d47f5	920	0 | ||||
| 0e9026a6-dd6c-4b79-9cac-a9930b97516d	59	0 | ||||
| 6ba05954-1cec-4bc2-9f4c-95847df12300	22	0 | ||||
| 2bcd1417-f014-4b7b-ac4d-926f3195ba0c	20	0 | ||||
| 299c29a8-c8e6-409c-adae-4ee4c1dd712d	47	0 | ||||
| 771d71cc-1361-4458-ad0d-88b04bb70e0b	149	0 | ||||
| e9020175-3881-458f-9f06-059b15e99bb1	23	0 | ||||
| cba36f72-0064-461b-9b25-77b1ba80a470	36	0 | ||||
| b9d602ca-f1d8-4f88-911f-52324b770736	38	95 | ||||
| ceefdc76-9677-43cd-b4e4-e059f7b51d4e	33	0 | ||||
| eae41ad3-5655-45e9-b049-7f1b17f825bb	23	23 | ||||
| 134c28ca-398c-4264-9b6a-90941cde61b9	22	21 | ||||
| 0ec93c8e-959c-4530-a43b-9e5ad385aaee	59	17 | ||||
| 0d466dfd-da83-4595-8a0a-bc64bef176ed	74	95 | ||||
| 14294494-663a-4abb-8f61-c058b973b466	22	0 | ||||
| 14f21608-7d36-460a-b34c-1154a1e941e3	21	0 | ||||
| 40352950-2a1e-4d63-a26a-1f331c1ad3df	908	0 | ||||
| d6fb62fb-8c4e-4874-82a2-807bcef37ca0	59	0 | ||||
| f9e645b0-b28c-45b5-a2fe-b96f5f40643f	129	321 | ||||
| f60e5503-8473-4338-9118-df2ccb412fa2	779	0 | ||||
| 4f1c78db-95bb-4d7d-89fb-e6f8408759a7	908	0 | ||||
| 11fa68dc-25a3-4f51-bce9-1621c6f81275	210	95 | ||||
| d5e11517-9c8a-4585-afd6-ea10b3002b75	60	20 | ||||
| 53d06988-053f-4183-b8d1-b1158f6171fc	20	0 | ||||
| d29041af-09b4-4610-8535-c53c8e790a6b	920	0 | ||||
| f49f7d13-9d82-48de-b36a-989a69a744fb	611	360 | ||||
| 5e4655e7-b3ef-4562-98de-6686f80b513f	30	16 | ||||
| 1a1b1883-ce7d-4c97-b541-f0d4627c35c2	738	0 | ||||
| f8078011-1f6d-420e-9f09-3407ec4a6727	919	0 | ||||
| f0a45b2b-5e1a-42c7-aa38-7bfde6e864c0	908	0 | ||||
| b40c66c3-274a-4c60-9347-496f1b8e9c19	621	359 | ||||
| dde919f4-fd66-4a5e-bb74-ca04dac293b5	800	4 | ||||
| e9577dd3-7e69-466f-bb05-d3058315b30f	23	0 | ||||
| 3c52ada9-b13e-4d50-856c-791efd58fada	907	0 | ||||
| 4371be67-b276-4e94-8255-6b80b4837b6f	800	0 | ||||
| 7d878da3-bf74-4eef-9a3f-b8292f12c77e	738	0 | ||||
| 82c99ec4-6e89-40ba-8a79-c9c56d3dd972	738	0 | ||||
| 9090f336-ddb9-4b9f-8c4c-341f1c96cbb6	738	0 | ||||
| d2a32fc1-8bc9-4a22-bf49-94a354f7718c	1146	358 | ||||
| ba2edc17-fd54-47ce-8f05-41d201df1b1f	106	95 | ||||
| 9a48860d-7f2e-40fe-a5d7-70ebc9f93bbe	23	0 | ||||
| dbbbc3cc-8a6e-4f3d-8c0a-0b739544d323	152	95 | ||||
| 14500ca6-4991-422a-91b7-9aeeaa21cae7	22	0 | ||||
| f69d76af-8548-48bd-810f-158a89e072d6	3860	0 | ||||
| 6b905451-38ce-4761-9ca3-0f0d08cc72cd	150	0 | ||||
| 727d4d54-245d-447c-bce8-156ce5cc36b1	150	0 | ||||
| 7a9a3310-eaf9-4fab-b4c2-64a8a39ccd3e	150	0 | ||||
| 0364493b-164d-4a33-a52b-b56261208d21	1010	0 | ||||
| e88e6c15-bb7b-4450-b675-3b459321024d	31	95 | ||||
| 7dfdb8ba-dce8-452b-a66c-64c4b8f63a13	150	0 | ||||
| cbf021bf-f349-4d1c-a6f4-76a8dfdd441c	36	0 | ||||
| ddb6fef5-9194-447d-b966-92649b623c54	939	0 | ||||
| a5f1a703-5ad8-43e3-8333-01f28f375964	937	0 | ||||
| eaaa58a3-5079-4013-84c9-9f7742c4c607	112	95 | ||||
| 06b6c01c-becc-4b93-b306-b6d9742a1e70	114	79 | ||||
| 0e396352-52d2-4807-b0f2-430202d2cbe1	93	79 | ||||
| f79539a9-4338-40be-a526-e737f4cdaa2a	116	79 | ||||
| 4a262bf4-e045-4f77-a1fd-521b2100b319	32	76 | ||||
| \. | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- Name: items items_pkey; Type: CONSTRAINT; Schema: public; Owner: dspacestatistics | ||||
| -- | ||||
|  | ||||
| ALTER TABLE ONLY public.items | ||||
|     ADD CONSTRAINT items_pkey PRIMARY KEY (id); | ||||
|  | ||||
|  | ||||
| -- | ||||
| -- PostgreSQL database dump complete | ||||
| -- | ||||
|  | ||||
							
								
								
									
										382
									
								
								tests/test_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								tests/test_api.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,382 @@ | ||||
| from falcon import testing | ||||
| import json | ||||
| import pytest | ||||
| from unittest.mock import patch | ||||
|  | ||||
| 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/fd8a46d5-1480-4e69-b187-cd3db96d8e4d") | ||||
|     response_doc = json.loads(response.text) | ||||
|  | ||||
|     assert isinstance(response_doc["downloads"], int) | ||||
|     assert isinstance(response_doc["id"], str) | ||||
|     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/c3910974-c3a5-4053-9dce-104aa7bb1620") | ||||
|  | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @pytest.mark.xfail | ||||
| def test_post_items_valid_dateFrom(client): | ||||
|     """Test POSTing a request with a valid dateFrom parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "dateFrom": "2020-01-01T00:00:00Z", | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 100 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 2 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_valid_dateFrom_mocked(client): | ||||
|     """Mock test POSTing a request with a valid dateFrom parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "dateFrom": "2020-01-01T00:00:00Z", | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     get_views_return_value = { | ||||
|         "fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 21, | ||||
|         "e53a2eab-1e31-448d-907b-3656ca4e86c1": 0, | ||||
|     } | ||||
|     get_downloads_return_value = { | ||||
|         "fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 575, | ||||
|         "e53a2eab-1e31-448d-907b-3656ca4e86c1": 899, | ||||
|     } | ||||
|  | ||||
|     with patch( | ||||
|         "dspace_statistics_api.app.get_views", return_value=get_views_return_value | ||||
|     ): | ||||
|         with patch( | ||||
|             "dspace_statistics_api.app.get_downloads", | ||||
|             return_value=get_downloads_return_value, | ||||
|         ): | ||||
|             response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 100 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 2 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_invalid_dateFrom(client): | ||||
|     """Test POSTing a request with an invalid dateFrom parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "dateFrom": "2020-01-01T00:00:00", | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 400 | ||||
|  | ||||
|  | ||||
| @pytest.mark.xfail | ||||
| def test_post_items_valid_dateTo(client): | ||||
|     """Test POSTing a request with a valid dateTo parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "dateTo": "2020-01-01T00:00:00Z", | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 100 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 2 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_valid_dateTo_mocked(client): | ||||
|     """Mock test POSTing a request with a valid dateTo parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "dateTo": "2020-01-01T00:00:00Z", | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     get_views_return_value = { | ||||
|         "fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 21, | ||||
|         "e53a2eab-1e31-448d-907b-3656ca4e86c1": 0, | ||||
|     } | ||||
|     get_downloads_return_value = { | ||||
|         "fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 575, | ||||
|         "e53a2eab-1e31-448d-907b-3656ca4e86c1": 899, | ||||
|     } | ||||
|  | ||||
|     with patch( | ||||
|         "dspace_statistics_api.app.get_views", return_value=get_views_return_value | ||||
|     ): | ||||
|         with patch( | ||||
|             "dspace_statistics_api.app.get_downloads", | ||||
|             return_value=get_downloads_return_value, | ||||
|         ): | ||||
|             response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 100 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 2 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_invalid_dateTo(client): | ||||
|     """Test POSTing a request with an invalid dateTo parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "dateFrom": "2020-01-01T00:00:00", | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 400 | ||||
|  | ||||
|  | ||||
| @pytest.mark.xfail | ||||
| def test_post_items_valid_limit(client): | ||||
|     """Test POSTing a request with a valid limit parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "limit": 1, | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 1 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 1 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_valid_limit_mocked(client): | ||||
|     """Mock test POSTing a request with a valid limit parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "limit": 1, | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     get_views_return_value = {"fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 21} | ||||
|     get_downloads_return_value = {"fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 575} | ||||
|  | ||||
|     with patch( | ||||
|         "dspace_statistics_api.app.get_views", return_value=get_views_return_value | ||||
|     ): | ||||
|         with patch( | ||||
|             "dspace_statistics_api.app.get_downloads", | ||||
|             return_value=get_downloads_return_value, | ||||
|         ): | ||||
|             response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 1 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 1 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_invalid_limit(client): | ||||
|     """Test POSTing a request with an invalid limit parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "limit": -1, | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 400 | ||||
|  | ||||
|  | ||||
| @pytest.mark.xfail | ||||
| def test_post_items_valid_page(client): | ||||
|     """Test POSTing a request with a valid page parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "page": 0, | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 100 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert response.json["totalPages"] == 0 | ||||
|     assert len(response.json["statistics"]) == 2 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_valid_page_mocked(client): | ||||
|     """Mock test POSTing a request with a valid page parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "page": 0, | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     get_views_return_value = { | ||||
|         "fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 21, | ||||
|         "e53a2eab-1e31-448d-907b-3656ca4e86c1": 0, | ||||
|     } | ||||
|     get_downloads_return_value = { | ||||
|         "fd8a46d5-1480-4e69-b187-cd3db96d8e4d": 575, | ||||
|         "e53a2eab-1e31-448d-907b-3656ca4e86c1": 899, | ||||
|     } | ||||
|  | ||||
|     with patch( | ||||
|         "dspace_statistics_api.app.get_views", return_value=get_views_return_value | ||||
|     ): | ||||
|         with patch( | ||||
|             "dspace_statistics_api.app.get_downloads", | ||||
|             return_value=get_downloads_return_value, | ||||
|         ): | ||||
|             response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 200 | ||||
|     assert response.json["limit"] == 100 | ||||
|     assert response.json["currentPage"] == 0 | ||||
|     assert isinstance(response.json["totalPages"], int) | ||||
|     assert len(response.json["statistics"]) == 2 | ||||
|     assert isinstance(response.json["statistics"][0]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][0]["downloads"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["views"], int) | ||||
|     assert isinstance(response.json["statistics"][1]["downloads"], int) | ||||
|  | ||||
|  | ||||
| def test_post_items_invalid_page(client): | ||||
|     """Test POSTing a request with an invalid page parameter in the request body.""" | ||||
|  | ||||
|     request_body = { | ||||
|         "page": -1, | ||||
|         "items": [ | ||||
|             "fd8a46d5-1480-4e69-b187-cd3db96d8e4d", | ||||
|             "e53a2eab-1e31-448d-907b-3656ca4e86c1", | ||||
|         ], | ||||
|     } | ||||
|  | ||||
|     response = client.simulate_post("/items", json=request_body) | ||||
|  | ||||
|     assert response.status_code == 400 | ||||
		Reference in New Issue
	
	Block a user