mirror of
				https://github.com/ilri/dspace-statistics-api.git
				synced 2025-10-31 12:51:15 +01:00 
			
		
		
		
	Compare commits
	
		
			250 Commits
		
	
	
		
			v0.5.2
			...
			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 | 
							
								
								
									
										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 | ||||
							
								
								
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -1,11 +0,0 @@ | ||||
| language: python | ||||
| python: | ||||
|   - "3.5" | ||||
|   - "3.6" | ||||
|   - "3.7-dev" | ||||
| script: pip install -r requirements.txt | ||||
| branches: | ||||
|   only: | ||||
|     - master | ||||
|  | ||||
| # vim: ts=2 sw=2 et | ||||
							
								
								
									
										137
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -4,26 +4,141 @@ 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). | ||||
|  | ||||
| ### [0.5.2] - 2018-10-28 | ||||
| ## Changed | ||||
| ## 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 | ||||
| ## [0.5.1] - 2018-10-24 | ||||
| ### Changed | ||||
| - Use Python's native json instead of ujson | ||||
|  | ||||
| ### [0.5.0] - 2018-10-24 | ||||
| ## Added | ||||
| ## [0.5.0] - 2018-10-24 | ||||
| ### Added | ||||
| - Example nginx configuration to README.md | ||||
|  | ||||
| ## Changed | ||||
| ### Changed | ||||
| - Don't initialize Solr connection in API | ||||
|  | ||||
| ### [0.4.3] - 2018-10-17 | ||||
| ## Changed | ||||
| ## [0.4.3] - 2018-10-17 | ||||
| ### Changed | ||||
| - Use pip install as script for Travis CI | ||||
|  | ||||
| ## Improved | ||||
| ### Improved | ||||
| - Documentation for deployment and testing | ||||
|  | ||||
| ## [0.4.2] - 2018-10-04 | ||||
| @@ -37,7 +152,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||||
|  | ||||
| ## [0.4.1] - 2018-09-26 | ||||
| ### Changed | ||||
| - Use execute_values() to batch insert records to PostgreSQL | ||||
| - Use `execute_values()` to batch insert records to PostgreSQL | ||||
|  | ||||
| ## [0.4.0] - 2018-09-25 | ||||
| ### Fixed | ||||
|   | ||||
							
								
								
									
										80
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,19 +1,30 @@ | ||||
| # DSpace Statistics API [](https://travis-ci.org/alanorth/dspace-statistics-api) | ||||
| A simple REST API to expose Solr view and download statistics for items in a DSpace repository. This project contains a standalone indexing component and a WSGI application. | ||||
| # 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. | ||||
|  | ||||
| - 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.5+ | ||||
| - Python 3.6+ | ||||
| - PostgreSQL version 9.5+ (due to [`UPSERT` support](https://wiki.postgresql.org/wiki/UPSERT)) | ||||
| - DSpace 4+ with [Solr usage statistics enabled](https://wiki.duraspace.org/display/DSDOC5x/SOLR+Statistics) | ||||
| - DSpace with [Solr usage statistics enabled](https://wiki.lyrasis.org/display/DSDOC5x/SOLR+Statistics) (tested with 5.8+ and 6.3) | ||||
|  | ||||
| ## Installation and Testing | ||||
| ## Installation | ||||
| Create a Python virtual environment and install the dependencies: | ||||
|  | ||||
|     $ python -m venv venv | ||||
|     $ . venv/bin/activate | ||||
|     $ 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 | ||||
| @@ -24,16 +35,25 @@ Set up the environment variables for Solr and PostgreSQL: | ||||
|  | ||||
| Index the Solr statistics core to populate the PostgreSQL database: | ||||
|  | ||||
|     $ ./indexer.py | ||||
|     $ python -m dspace_statistics_api.indexer | ||||
|  | ||||
| Run the REST API: | ||||
|  | ||||
|     $ gunicorn app: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. | ||||
|  | ||||
| @@ -59,21 +79,43 @@ This would expose the API at `/rest/statistics`. | ||||
| ## Using the API | ||||
| The API exposes the following endpoints: | ||||
|  | ||||
|   - GET `/items` — return views and downloads for all items that Solr knows about¹. Accepts `limit` and `page` query parameters for pagination of results. | ||||
|   - GET `/item/id` — return views and downloads for a single item (*id* must be a positive integer). Returns HTTP 404 if an item id is not found. | ||||
|   - 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. | ||||
|  | ||||
| ¹ We are querying the Solr statistics core, which technically only knows about items that have either views or downloads. | ||||
| The item id is the *internal* UUID for an item. You can get these from the standard DSpace REST API. | ||||
|  | ||||
| ## Todo | ||||
| ¹ 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 | ||||
|  | ||||
| - Add API documentation | ||||
| - Close DB connection when gunicorn shuts down gracefully | ||||
| - Better logging | ||||
| - Tests | ||||
| - Check if database exists (try/except) | ||||
| - Version API | ||||
| - Version API (or at least include a /version endpoint?) | ||||
| - Use JSON in PostgreSQL | ||||
| - Switch to [Python 3.6+ f-string syntax](https://realpython.com/python-f-strings/) | ||||
| - 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)). | ||||
|   | ||||
							
								
								
									
										73
									
								
								app.py
									
									
									
									
									
								
							
							
						
						
									
										73
									
								
								app.py
									
									
									
									
									
								
							| @@ -1,73 +0,0 @@ | ||||
| from database import database_connection | ||||
| import falcon | ||||
|  | ||||
| db = database_connection() | ||||
| db.set_session(readonly=True) | ||||
|  | ||||
| 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=0, max=100) or 100 | ||||
|         page = req.get_param_as_int("page", min=0) or 0 | ||||
|         offset = limit * page | ||||
|  | ||||
|         cursor = db.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, ordered by id, and use limit and offset to page through results | ||||
|         cursor.execute('SELECT id, views, downloads FROM items ORDER BY id ASC LIMIT {} OFFSET {}'.format(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': item['id'], 'views': item['views'], 'downloads': item['downloads'] }) | ||||
|  | ||||
|         cursor.close() | ||||
|  | ||||
|         message = { | ||||
|                 'currentPage': page, | ||||
|                 'totalPages': pages, | ||||
|                 'limit': limit, | ||||
|                 'statistics': statistics | ||||
|         } | ||||
|  | ||||
|         resp.media = message | ||||
|  | ||||
| class ItemResource: | ||||
|     def on_get(self, req, resp, item_id): | ||||
|         """Handles GET requests""" | ||||
|  | ||||
|         cursor = db.cursor() | ||||
|         cursor.execute('SELECT views, downloads FROM items WHERE id={}'.format(item_id)) | ||||
|         if cursor.rowcount == 0: | ||||
|             raise falcon.HTTPNotFound( | ||||
|                     title='Item not found', | ||||
|                     description='The item with id "{}" was not found.'.format(item_id) | ||||
|             ) | ||||
|         else: | ||||
|             results = cursor.fetchone() | ||||
|  | ||||
|             statistics = { | ||||
|                 'id': item_id, | ||||
|                 'views': results['views'], | ||||
|                 'downloads': results['downloads'] | ||||
|             } | ||||
|  | ||||
|             resp.media = statistics | ||||
|  | ||||
|         cursor.close() | ||||
|  | ||||
| def on_exit(api): | ||||
|     print("Shutting down DB") | ||||
|  | ||||
| api = falcon.API() | ||||
| api.add_route('/items', AllItemsResource()) | ||||
| api.add_route('/item/{item_id:int}', ItemResource()) | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										11
									
								
								config.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								config.py
									
									
									
									
									
								
							| @@ -1,11 +0,0 @@ | ||||
| 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') | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
| @@ -12,7 +12,7 @@ Group=nogroup | ||||
| WorkingDirectory=/var/lib/dspace-statistics-api | ||||
| ExecStart=/var/lib/dspace-statistics-api/venv/bin/gunicorn \ | ||||
|           --bind 127.0.0.1:5000                             \ | ||||
|           app:api | ||||
|           dspace_statistics_api.app | ||||
| ExecReload=/bin/kill -s HUP $MAINPID | ||||
| ExecStop=/bin/kill -s TERM $MAINPID | ||||
|  | ||||
|   | ||||
| @@ -11,7 +11,7 @@ Environment=DATABASE_HOST=localhost | ||||
| User=nobody | ||||
| Group=nogroup | ||||
| WorkingDirectory=/var/lib/dspace-statistics-api | ||||
| ExecStart=/var/lib/dspace-statistics-api/venv/bin/python indexer.py | ||||
| ExecStart=/var/lib/dspace-statistics-api/venv/bin/python -m dspace_statistics_api.indexer | ||||
|  | ||||
| [Install] | ||||
| WantedBy=multi-user.target | ||||
|   | ||||
							
								
								
									
										12
									
								
								database.py
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								database.py
									
									
									
									
									
								
							| @@ -1,12 +0,0 @@ | ||||
| from config import DATABASE_NAME | ||||
| from config import DATABASE_USER | ||||
| from config import DATABASE_PASS | ||||
| from config import DATABASE_HOST | ||||
| import psycopg2, psycopg2.extras | ||||
|  | ||||
| def database_connection(): | ||||
|     connection = psycopg2.connect("dbname={} user={} password={} host='{}'".format(DATABASE_NAME, DATABASE_USER, DATABASE_PASS, DATABASE_HOST), cursor_factory=psycopg2.extras.DictCursor) | ||||
|  | ||||
|     return connection | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										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() | ||||
							
								
								
									
										173
									
								
								indexer.py
									
									
									
									
									
								
							
							
						
						
									
										173
									
								
								indexer.py
									
									
									
									
									
								
							| @@ -1,173 +0,0 @@ | ||||
| #!/usr/bin/env python | ||||
| # | ||||
| # 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.5+ and requires several modules that you | ||||
| # can install with pip (I recommend using a Python virtual environment): | ||||
| # | ||||
| #   $ pip install SolrClient psycopg2-binary | ||||
| # | ||||
| # See: https://solrclient.readthedocs.io/en/latest/SolrClient.html | ||||
| # See: https://wiki.duraspace.org/display/DSPACE/Solr | ||||
|  | ||||
| from database import database_connection | ||||
| import json | ||||
| import psycopg2.extras | ||||
| from solr import solr_connection | ||||
|  | ||||
| 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. | ||||
|     # | ||||
|     # see: https://lucene.apache.org/solr/guide/6_6/the-stats-component.html | ||||
|     res = solr.query('statistics', { | ||||
|         'q':'type:2', | ||||
|         'fq':'isBot:false 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 | ||||
|     }, rows=0) | ||||
|  | ||||
|     # get total number of distinct facets (countDistinct) | ||||
|     results_totalNumFacets = json.loads(res.get_json())['stats']['stats_fields']['id']['countDistinct'] | ||||
|  | ||||
|     # 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 | ||||
|  | ||||
|     cursor = db.cursor() | ||||
|  | ||||
|     # create an empty list to store values for batch insertion | ||||
|     data = [] | ||||
|  | ||||
|     while results_current_page <= results_num_pages: | ||||
|         print('Indexing item views (page {} of {})'.format(results_current_page, results_num_pages)) | ||||
|  | ||||
|         res = solr.query('statistics', { | ||||
|             'q':'type:2', | ||||
|             'fq':'isBot:false 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 | ||||
|         }, rows=0) | ||||
|  | ||||
|         # SolrClient's get_facets() returns a dict of dicts | ||||
|         views = res.get_facets() | ||||
|         # in this case 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 | ||||
|  | ||||
|     cursor.close() | ||||
|  | ||||
| def index_downloads(): | ||||
|     # get the total number of distinct facets for items with at least 1 download | ||||
|     res = solr.query('statistics', { | ||||
|         'q':'type:0', | ||||
|         'fq':'isBot:false 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 | ||||
|     }, rows=0) | ||||
|  | ||||
|     # get total number of distinct facets (countDistinct) | ||||
|     results_totalNumFacets = json.loads(res.get_json())['stats']['stats_fields']['owningItem']['countDistinct'] | ||||
|  | ||||
|     # 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 | ||||
|  | ||||
|     cursor = db.cursor() | ||||
|  | ||||
|     # create an empty list to store values for batch insertion | ||||
|     data = [] | ||||
|  | ||||
|     while results_current_page <= results_num_pages: | ||||
|         print('Indexing item downloads (page {} of {})'.format(results_current_page, results_num_pages)) | ||||
|  | ||||
|         res = solr.query('statistics', { | ||||
|             'q':'type:0', | ||||
|             'fq':'isBot:false 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 | ||||
|         }, rows=0) | ||||
|  | ||||
|         # SolrClient's get_facets() returns a dict of dicts | ||||
|         downloads = res.get_facets() | ||||
|         # in this case 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 | ||||
|  | ||||
|     cursor.close() | ||||
|  | ||||
| db = database_connection() | ||||
| solr = solr_connection() | ||||
|  | ||||
| # create table to store item views and downloads | ||||
| cursor = db.cursor() | ||||
| cursor.execute('''CREATE TABLE IF NOT EXISTS items | ||||
|                   (id INT PRIMARY KEY, views INT DEFAULT 0, downloads INT DEFAULT 0)''') | ||||
| index_views() | ||||
| index_downloads() | ||||
|  | ||||
| db.close() | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										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 | ||||
| @@ -1,12 +1,66 @@ | ||||
| certifi==2018.10.15 | ||||
| chardet==3.0.4 | ||||
| falcon==1.4.1 | ||||
| gunicorn==19.9.0 | ||||
| idna==2.7 | ||||
| kazoo==2.5.0 | ||||
| psycopg2-binary==2.7.5 | ||||
| python-mimeparse==1.6.0 | ||||
| requests==2.20.0 | ||||
| six==1.11.0 | ||||
| -e git://github.com/alanorth/SolrClient.git@c629e3475be37c82770b2be61748be7e29882648#egg=SolrClient | ||||
| urllib3==1.24 | ||||
| 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 | ||||
							
								
								
									
										9
									
								
								solr.py
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								solr.py
									
									
									
									
									
								
							| @@ -1,9 +0,0 @@ | ||||
| from config import SOLR_SERVER | ||||
| from SolrClient import SolrClient | ||||
|  | ||||
| def solr_connection(): | ||||
|     connection = SolrClient(SOLR_SERVER) | ||||
|  | ||||
|     return connection | ||||
|  | ||||
| # vim: set sw=4 ts=4 expandtab: | ||||
							
								
								
									
										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