├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── scraper-test.yaml ├── .gitignore ├── Dockerfile ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── airflow ├── dags │ └── main_dag.py └── tasks │ ├── __init__.py │ ├── configuration.env.example │ ├── dbt_transform │ ├── dbt_project.yml │ ├── generate_dbt_profile.py │ └── models │ │ ├── avg_audio_signature_headphone.sql │ │ ├── avg_audio_signature_iem.sql │ │ ├── avg_driver_rating_headphone.sql │ │ ├── highest_rank_company_headphone.sql │ │ ├── highest_rank_company_iem.sql │ │ ├── highest_value_company_headphone.sql │ │ ├── highest_value_company_iem.sql │ │ ├── schema.yml │ │ └── staging │ │ ├── schema.yml │ │ ├── stg_companynames.sql │ │ └── stg_maprankvalues.sql │ ├── rds_load │ ├── queries │ │ ├── create_tables.sql │ │ ├── load_main_tables.sql │ │ └── load_temp_tables.sql │ └── upload_to_rds.py │ ├── redshift_load │ ├── queries │ │ ├── create_tables.sql │ │ ├── load_main_tables.sql │ │ └── load_temp_tables.sql │ └── upload_to_redshift.py │ ├── scraper_extract │ ├── __init__.py │ ├── models.py │ ├── scraper.py │ └── test_scraper.py │ ├── upload_to_s3.py │ ├── utilities.py │ └── validate_sanitize_bronze.py ├── docker-compose.yaml ├── generate_config.sh ├── images ├── architecture.jpeg └── metabase_dashboard.jpeg ├── pyproject.toml └── terraform ├── budget.tf ├── output.tf ├── provider.tf ├── rds.tf ├── redshift.tf ├── s3.tf └── variable.tf /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | # 4 space indentation 12 | [*.{py}] 13 | indent_style = space 14 | indent_size = 4 15 | profile = black 16 | 17 | # 2 space indentation 18 | [*.{js,json,y{a,}ml,html,cwl}] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.{md,Rmd,rst}] 23 | trim_trailing_whitespace = false 24 | indent_style = space 25 | indent_size = 2 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sql linguist-detectable=true 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | 9 | # Maintain dependencies for pipenv 10 | - package-ecosystem: "pip" 11 | directory: "/" 12 | schedule: 13 | interval: "monthly" 14 | -------------------------------------------------------------------------------- /.github/workflows/scraper-test.yaml: -------------------------------------------------------------------------------- 1 | name: Testing the Scraper 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | 11 | jobs: 12 | build_tests: 13 | runs-on: ubuntu-latest 14 | steps: 15 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 16 | - name: Checkout code 17 | uses: actions/checkout@v3 18 | - name: Setup Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: 3.9 22 | # Setup .env file for secrets access 23 | - name: Install requirements 24 | run: | 25 | pip install pipenv 26 | pipenv requirements > requirements.txt 27 | pip install -r requirements.txt 28 | - name: Run tests 29 | run: | 30 | pytest 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/configuration.yaml 2 | **/*.env 3 | profiles.yml 4 | .user.yml 5 | 6 | # dbt 7 | **target/ 8 | **dbt_packages/ 9 | **logs/ 10 | 11 | 12 | # Airflow 13 | **/logs/* 14 | 15 | # Local .terraform directories 16 | **/.terraform/* 17 | *.hcl 18 | 19 | # .tfstate files 20 | *.tfstate 21 | *.tfstate.* 22 | 23 | # Crash log files 24 | crash.log 25 | crash.*.log 26 | 27 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 28 | # password, private keys, and other secrets. These should not be part of version 29 | # control as they are data points which are potentially sensitive and subject 30 | # to change depending on the environment. 31 | *.tfvars 32 | *.tfvars.json 33 | 34 | # Ignore override files as they are usually used to override resources locally and so 35 | # are not checked in 36 | override.tf 37 | override.tf.json 38 | *_override.tf 39 | *_override.tf.json 40 | 41 | # Include override files you do wish to add to version control using negated pattern 42 | # !example_override.tf 43 | 44 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 45 | # example: *tfplan* 46 | 47 | # Ignore CLI configuration files 48 | .terraformrc 49 | terraform.rc 50 | 51 | # Project specific 52 | *csv 53 | 54 | # Byte-compiled / optimized / DLL files 55 | __pycache__/ 56 | *.py[cod] 57 | *$py.class 58 | 59 | # C extensions 60 | *.so 61 | 62 | # Distribution / packaging 63 | .Python 64 | build/ 65 | develop-eggs/ 66 | dist/ 67 | downloads/ 68 | eggs/ 69 | .eggs/ 70 | lib/ 71 | lib64/ 72 | parts/ 73 | sdist/ 74 | var/ 75 | wheels/ 76 | pip-wheel-metadata/ 77 | share/python-wheels/ 78 | *.egg-info/ 79 | .installed.cfg 80 | *.egg 81 | MANIFEST 82 | 83 | # PyInstaller 84 | # Usually these files are written by a python script from a template 85 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 86 | *.manifest 87 | *.spec 88 | 89 | # Installer logs 90 | pip-log.txt 91 | pip-delete-this-directory.txt 92 | 93 | # Unit test / coverage reports 94 | htmlcov/ 95 | .tox/ 96 | .nox/ 97 | .coverage 98 | .coverage.* 99 | .cache 100 | nosetests.xml 101 | coverage.xml 102 | *.cover 103 | *.py,cover 104 | .hypothesis/ 105 | .pytest_cache/ 106 | 107 | # Translations 108 | *.mo 109 | *.pot 110 | 111 | # Django stuff: 112 | *.log 113 | local_settings.py 114 | db.sqlite3 115 | db.sqlite3-journal 116 | 117 | # Flask stuff: 118 | instance/ 119 | .webassets-cache 120 | 121 | # Scrapy stuff: 122 | .scrapy 123 | 124 | # Sphinx documentation 125 | docs/_build/ 126 | 127 | # PyBuilder 128 | target/ 129 | 130 | # Jupyter Notebook 131 | .ipynb_checkpoints 132 | 133 | # IPython 134 | profile_default/ 135 | ipython_config.py 136 | 137 | # pyenv 138 | .python-version 139 | 140 | # pipenv 141 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 142 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 143 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 144 | # install all needed dependencies. 145 | #Pipfile.lock 146 | 147 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 148 | __pypackages__/ 149 | 150 | # Celery stuff 151 | celerybeat-schedule 152 | celerybeat.pid 153 | 154 | # SageMath parsed files 155 | *.sage.py 156 | 157 | # Environments 158 | .env 159 | .venv 160 | env/ 161 | venv/ 162 | ENV/ 163 | env.bak/ 164 | venv.bak/ 165 | 166 | # Spyder project settings 167 | .spyderproject 168 | .spyproject 169 | 170 | # Rope project settings 171 | .ropeproject 172 | 173 | # mkdocs documentation 174 | /site 175 | 176 | # mypy 177 | .mypy_cache/ 178 | .dmypy.json 179 | dmypy.json 180 | 181 | # Pyre type checker 182 | .pyre/ 183 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM apache/airflow:latest-python3.9 2 | 3 | USER root 4 | 5 | RUN apt-get update \ 6 | && apt-get install -y --no-install-recommends \ 7 | build-essential \ 8 | && apt-get autoremove -yqq --purge \ 9 | && apt-get -y install libpq-dev gcc \ 10 | && apt-get clean \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | USER airflow 14 | 15 | COPY /Pipfile /Pipfile 16 | COPY /Pipfile.lock /Pipfile.lock 17 | 18 | # Dependencies 19 | RUN pip install --upgrade pip 20 | RUN pip install pipenv 21 | RUN pipenv requirements > requirements.txt 22 | RUN pip install --no-cache-dir --user -r requirements.txt 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | help: 2 | @echo " build Builds the docker images for the docker-compose setup" 3 | @echo " clean Stops and removes all docker containers" 4 | @echo " shell Opens a Bash shell" 5 | @echo " redis-cli Opens a Redis CLI" 6 | @echo " run Run a airflow command" 7 | @echo " stop Stops the docker containers" 8 | @echo " up Runs the whole stack, served under http://localhost:8080/" 9 | @echo " init Initializes airflow services" 10 | @echo " base-build Builds the base docker image for airflow" 11 | @echo " test UI tests for scraper" 12 | @echo " config Generate a configuration using terraform outputs" 13 | @echo " test-dbt Run tests for dbt build" 14 | @echo " infra Create AWS infrastructure" 15 | 16 | build: 17 | docker-compose build 18 | 19 | clean: stop 20 | docker-compose rm -f 21 | rm -rf logs/* 22 | if [ -f airflow-worker.pid ]; then rm airflow-worker.pid; fi 23 | 24 | shell: 25 | docker-compose run web bash 26 | 27 | redis-cli: 28 | docker-compose run redis redis-cli -h redis 29 | 30 | run: 31 | docker-compose run -v ~/.aws/:/root/.aws web airflow $(COMMAND) 32 | 33 | stop: 34 | docker-compose down 35 | docker-compose stop 36 | 37 | up: 38 | docker-compose up 39 | 40 | init: 41 | docker-compose up airflow-init 42 | 43 | base-build: 44 | docker build -t airflow-extended:latest -f Dockerfile . 45 | 46 | test-scraper: 47 | pytest 48 | 49 | test-dbt: 50 | dbt test 51 | 52 | config: 53 | chmod +x generate_config.sh 54 | ./generate_config.sh 55 | 56 | infra: 57 | terraform -chdir=terraform/ init -input=false 58 | terraform -chdir=terraform/ apply 59 | 60 | 61 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | requests = "*" 8 | beautifulsoup4 = "*" 9 | mypy = "*" 10 | pyyaml = "*" 11 | boto3 = "*" 12 | pytest = "*" 13 | pydantic = "*" 14 | pandas = "*" 15 | numpy = "*" 16 | psycopg2 = "*" 17 | python-dotenv = "*" 18 | dbt-redshift = "*" 19 | airflow-dbt = "*" 20 | 21 | [dev-packages] 22 | black = "*" 23 | sqlfluff = "*" 24 | 25 | [requires] 26 | python_version = "3.9" 27 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "cd511062c734f23d29c3d42ded6f5076b9ea0123287f5680c6bf327cca501f1b" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.9" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "agate": { 20 | "hashes": [ 21 | "sha256:2d568fd68a8eb8b56c805a1299ba4bc30ca0434563be1bea309c9d1c1c8401f4", 22 | "sha256:e0f2f813f7e12311a4cdccc97d6ba0a6781e9c1aa8eca0ab00d5931c0113a308" 23 | ], 24 | "version": "==1.6.3" 25 | }, 26 | "airflow-dbt": { 27 | "hashes": [ 28 | "sha256:a662f3bd5db13d35d326b7ce16b5947ad0f7d54b233ba75544898dce06076d36", 29 | "sha256:d077a6d26e0571724f050673becf64a5e4c429c1d0ee3f90611e07e3f4469ec8" 30 | ], 31 | "index": "pypi", 32 | "version": "==0.4.0" 33 | }, 34 | "alembic": { 35 | "hashes": [ 36 | "sha256:0a024d7f2de88d738d7395ff866997314c837be6104e90c5724350313dee4da4", 37 | "sha256:cd0b5e45b14b706426b833f06369b9a6d5ee03f826ec3238723ce8caaf6e5ffa" 38 | ], 39 | "markers": "python_version >= '3.7'", 40 | "version": "==1.8.1" 41 | }, 42 | "anyio": { 43 | "hashes": [ 44 | "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b", 45 | "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be" 46 | ], 47 | "markers": "python_full_version >= '3.6.2'", 48 | "version": "==3.6.1" 49 | }, 50 | "apache-airflow": { 51 | "hashes": [ 52 | "sha256:1c73cb970bd0f495f790bb11bd3aacc65d180bc6e64a3a28fd14fd1848b51916", 53 | "sha256:bf87ce18b845c0cf2d9e7850463821305dcca8b0e71e7ffd77675159d2931541" 54 | ], 55 | "markers": "python_version ~= '3.7'", 56 | "version": "==2.3.2" 57 | }, 58 | "apache-airflow-providers-common-sql": { 59 | "hashes": [ 60 | "sha256:510cd732014248514196727e649ed2c70960f3c4a73b62b4c21a2a58bd92b445", 61 | "sha256:729d2b26a64c71a6983511c2df88ab96d9c87ef55da1b6f2844dbd0202ef101c" 62 | ], 63 | "markers": "python_version ~= '3.7'", 64 | "version": "==1.2.0" 65 | }, 66 | "apache-airflow-providers-ftp": { 67 | "hashes": [ 68 | "sha256:6b8bae4fae47c03974e467fc0b8cafd0c29418d710b2b7b32f1c11a543892168", 69 | "sha256:95c442cea30e78a6d8e29a805808d52f451acd2074deb5cf077881a1818187ba" 70 | ], 71 | "markers": "python_version ~= '3.7'", 72 | "version": "==3.1.0" 73 | }, 74 | "apache-airflow-providers-http": { 75 | "hashes": [ 76 | "sha256:192532fb6cba0e36c926d9030a70c83b78e5e7ec4dc6b3a5b04b2e80bc03431f", 77 | "sha256:791086dd7ca1b2f9821023665fe3903d2235ca962cf4debaa57d8114ed09a69a" 78 | ], 79 | "markers": "python_version ~= '3.7'", 80 | "version": "==4.0.0" 81 | }, 82 | "apache-airflow-providers-imap": { 83 | "hashes": [ 84 | "sha256:4139b04e84a40cd4b3204e6fd87f360253e2f2dc877c1f132bd0bd379cdcc918", 85 | "sha256:deb631f2b3a62e74ed60ad67da699ada1bb9456c96469842a1be3d14683492cb" 86 | ], 87 | "markers": "python_version ~= '3.7'", 88 | "version": "==3.0.0" 89 | }, 90 | "apache-airflow-providers-sqlite": { 91 | "hashes": [ 92 | "sha256:51560817375206e792d2bf254b4ba2395ff243931c738c35658df5d55cb68fc7", 93 | "sha256:803e9e404342bdf25385b54a37158a5c521bfc0a8040e95a48480df5b5862135" 94 | ], 95 | "markers": "python_version ~= '3.7'", 96 | "version": "==3.2.1" 97 | }, 98 | "apispec": { 99 | "extras": [ 100 | "yaml" 101 | ], 102 | "hashes": [ 103 | "sha256:a1df9ec6b2cd0edf45039ef025abd7f0660808fa2edf737d3ba1cf5ef1a4625b", 104 | "sha256:d23ebd5b71e541e031b02a19db10b5e6d5ef8452c552833e3e1afc836b40b1ad" 105 | ], 106 | "markers": "python_version >= '3.5'", 107 | "version": "==3.3.2" 108 | }, 109 | "argcomplete": { 110 | "hashes": [ 111 | "sha256:6372ad78c89d662035101418ae253668445b391755cfe94ea52f1b9d22425b20", 112 | "sha256:cffa11ea77999bb0dd27bb25ff6dc142a6796142f68d45b1a26b11f58724561e" 113 | ], 114 | "markers": "python_version >= '3.6'", 115 | "version": "==2.0.0" 116 | }, 117 | "attrs": { 118 | "hashes": [ 119 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 120 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 121 | ], 122 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 123 | "version": "==20.3.0" 124 | }, 125 | "babel": { 126 | "hashes": [ 127 | "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", 128 | "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" 129 | ], 130 | "markers": "python_version >= '3.6'", 131 | "version": "==2.10.3" 132 | }, 133 | "beautifulsoup4": { 134 | "hashes": [ 135 | "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", 136 | "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" 137 | ], 138 | "index": "pypi", 139 | "version": "==4.11.1" 140 | }, 141 | "blinker": { 142 | "hashes": [ 143 | "sha256:1eb563df6fdbc39eeddc177d953203f99f097e9bf0e2b8f9f3cf18b6ca425e36", 144 | "sha256:923e5e2f69c155f2cc42dafbbd70e16e3fde24d2d4aa2ab72fbe386238892462" 145 | ], 146 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 147 | "version": "==1.5" 148 | }, 149 | "boto3": { 150 | "hashes": [ 151 | "sha256:346f8f0d101a4261dac146a959df18d024feda6431e1d9d84f94efd24d086cae", 152 | "sha256:d0d8ffcdc10821c4562bc7f935cdd840033bbc342ac0e14b6bdd348b3adf4c04" 153 | ], 154 | "index": "pypi", 155 | "version": "==1.24.89" 156 | }, 157 | "botocore": { 158 | "hashes": [ 159 | "sha256:238f1dfdb8d8d017c2aea082609a3764f3161d32745900f41bcdcf290d95a048", 160 | "sha256:621f5413be8f97712b7e36c1b075a8791d1d1b9971a7ee060cdcdf5e2debf6c1" 161 | ], 162 | "markers": "python_version >= '3.7'", 163 | "version": "==1.27.89" 164 | }, 165 | "cachelib": { 166 | "hashes": [ 167 | "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5", 168 | "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3" 169 | ], 170 | "markers": "python_version >= '3.7'", 171 | "version": "==0.9.0" 172 | }, 173 | "cattrs": { 174 | "hashes": [ 175 | "sha256:211800f725cdecedcbcf4c753bbd22d248312b37d130f06045434acb7d9b34e1", 176 | "sha256:35dd9063244263e63bd0bd24ea61e3015b00272cead084b2c40d788b0f857c46" 177 | ], 178 | "markers": "python_version >= '3.7' and python_version < '4.0'", 179 | "version": "==1.10.0" 180 | }, 181 | "certifi": { 182 | "hashes": [ 183 | "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", 184 | "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" 185 | ], 186 | "markers": "python_version >= '3.6'", 187 | "version": "==2022.9.24" 188 | }, 189 | "cffi": { 190 | "hashes": [ 191 | "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", 192 | "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", 193 | "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", 194 | "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", 195 | "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", 196 | "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", 197 | "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", 198 | "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", 199 | "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", 200 | "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", 201 | "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", 202 | "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", 203 | "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", 204 | "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", 205 | "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", 206 | "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", 207 | "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", 208 | "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", 209 | "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", 210 | "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", 211 | "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", 212 | "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", 213 | "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", 214 | "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", 215 | "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", 216 | "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", 217 | "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", 218 | "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", 219 | "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", 220 | "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", 221 | "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", 222 | "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", 223 | "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", 224 | "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", 225 | "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", 226 | "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", 227 | "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", 228 | "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", 229 | "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", 230 | "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", 231 | "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", 232 | "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", 233 | "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", 234 | "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", 235 | "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", 236 | "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", 237 | "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", 238 | "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", 239 | "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", 240 | "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", 241 | "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", 242 | "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", 243 | "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", 244 | "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", 245 | "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", 246 | "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", 247 | "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", 248 | "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", 249 | "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", 250 | "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", 251 | "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", 252 | "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", 253 | "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", 254 | "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" 255 | ], 256 | "version": "==1.15.1" 257 | }, 258 | "charset-normalizer": { 259 | "hashes": [ 260 | "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", 261 | "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" 262 | ], 263 | "markers": "python_full_version >= '3.6.0'", 264 | "version": "==2.1.1" 265 | }, 266 | "click": { 267 | "hashes": [ 268 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 269 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 270 | ], 271 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 272 | "version": "==7.1.2" 273 | }, 274 | "clickclick": { 275 | "hashes": [ 276 | "sha256:4efb13e62353e34c5eef7ed6582c4920b418d7dedc86d819e22ee089ba01802c", 277 | "sha256:c8f33e6d9ec83f68416dd2136a7950125bd256ec39ccc9a85c6e280a16be2bb5" 278 | ], 279 | "version": "==20.10.2" 280 | }, 281 | "colorama": { 282 | "hashes": [ 283 | "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", 284 | "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" 285 | ], 286 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 287 | "version": "==0.4.5" 288 | }, 289 | "colorlog": { 290 | "hashes": [ 291 | "sha256:3dd15cb27e8119a24c1a7b5c93f9f3b455855e0f73993b1c25921b2f646f1dcd", 292 | "sha256:59b53160c60902c405cdec28d38356e09d40686659048893e026ecbd589516b1" 293 | ], 294 | "version": "==4.8.0" 295 | }, 296 | "commonmark": { 297 | "hashes": [ 298 | "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", 299 | "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" 300 | ], 301 | "version": "==0.9.1" 302 | }, 303 | "connexion": { 304 | "extras": [ 305 | "flask", 306 | "swagger-ui" 307 | ], 308 | "hashes": [ 309 | "sha256:99aa5781e70a7b94f8ffae8cf89f309d49cdb811bbd65a8e2f2546f3b19a01e6", 310 | "sha256:f343717241b4c4802a694c38fee66fb1693c897fe4ea5a957fa9b3b07caf6394" 311 | ], 312 | "markers": "python_version >= '3.6'", 313 | "version": "==2.14.1" 314 | }, 315 | "cron-descriptor": { 316 | "hashes": [ 317 | "sha256:8e83f08dbd76657a0b6ce1168b293bc27fa68be573ef8cb086ef0d9896340876" 318 | ], 319 | "version": "==1.2.31" 320 | }, 321 | "croniter": { 322 | "hashes": [ 323 | "sha256:12369c67e231c8ce5f98958d76ea6e8cb5b157fda4da7429d245a931e4ed411e", 324 | "sha256:72ef78d0f8337eb35393b8893ebfbfbeb340f2d2ae47e0d2d78130e34b0dd8b9" 325 | ], 326 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 327 | "version": "==1.3.7" 328 | }, 329 | "cryptography": { 330 | "hashes": [ 331 | "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", 332 | "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", 333 | "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", 334 | "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", 335 | "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", 336 | "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", 337 | "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", 338 | "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", 339 | "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", 340 | "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", 341 | "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", 342 | "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", 343 | "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", 344 | "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", 345 | "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", 346 | "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", 347 | "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", 348 | "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", 349 | "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", 350 | "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", 351 | "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", 352 | "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", 353 | "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", 354 | "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", 355 | "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", 356 | "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" 357 | ], 358 | "markers": "python_version >= '3.6'", 359 | "version": "==38.0.1" 360 | }, 361 | "dbt-core": { 362 | "hashes": [ 363 | "sha256:7dcd6e22c4022b2cc500c072790484f2cde635bfc768482703676690ff82b23d", 364 | "sha256:b93895380dfe481d7f6ded9203ca4357fe6309c19643e27627612f174f942689" 365 | ], 366 | "markers": "python_full_version >= '3.7.2'", 367 | "version": "==1.2.2" 368 | }, 369 | "dbt-extractor": { 370 | "hashes": [ 371 | "sha256:037907a7c7ae0391045d81338ca77ddaef899a91d80f09958f09fe374594e19b", 372 | "sha256:34783d788b133f223844e280e37b3f5244f2fb60acc457aa75c2667e418d5442", 373 | "sha256:35265a0ae0a250623b0c2e3308b2738dc8212e40e0aa88407849e9ea090bb312", 374 | "sha256:3fe8d8e28a7bd3e0884896147269ca0202ca432d8733113386bdc84c824561bf", 375 | "sha256:4dc715bd740e418d8dc1dd418fea508e79208a24cf5ab110b0092a3cbe96bf71", 376 | "sha256:554d27741a54599c39e5c0b7dbcab77400d83f908caba284a3e960db812e5814", 377 | "sha256:75b1c665699ec0f1ffce1ba3d776f7dfce802156f22e70a7b9c8f0b4d7e80f42", 378 | "sha256:76872cdee659075d6ce2df92dc62e59a74ba571be62acab2e297ca478b49d766", 379 | "sha256:7c291f9f483eae4f60dd5859097d7ba51d5cb6c4725f08973ebd18cdea89d758", 380 | "sha256:7d7c47774dc051b8c18690281a55e2e3d3320e823b17e04b06bc3ff81b1874ba", 381 | "sha256:81435841610be1b07806d72cd89b1956c6e2a84c360b9ceb3f949c62a546d569", 382 | "sha256:822b1e911db230e1b9701c99896578e711232001027b518c44c32f79a46fa3f9", 383 | "sha256:9da211869a1220ea55c5552c1567a3ea5233a6c52fa89ca87a22465481c37bc9", 384 | "sha256:a805d51a25317f53cbff951c79b9cf75421cf48e4b3e1dfb3e9e8de6d824b76c", 385 | "sha256:bc9e0050e3a2f4ea9fe58e8794bc808e6709a0c688ed710fc7c5b6ef3e5623ec", 386 | "sha256:cad90ddc708cb4182dc16fe2c87b1f088a1679877b93e641af068eb68a25d582" 387 | ], 388 | "markers": "python_full_version >= '3.6.1'", 389 | "version": "==0.4.1" 390 | }, 391 | "dbt-postgres": { 392 | "hashes": [ 393 | "sha256:d86f9b817ff790a5c85038ef6ab98fdc8e42b604e94749c0aa58d0d9e7f64aa7", 394 | "sha256:f607e2ab6bef900f1dfd57da8114188e6e8040002c713e48ce2394162a2c6adf" 395 | ], 396 | "markers": "python_version >= '3.7'", 397 | "version": "==1.2.2" 398 | }, 399 | "dbt-redshift": { 400 | "hashes": [ 401 | "sha256:889df16d4a33f8fc9e940dbe08f8b774dba1a2a8ed141fe25de8c567d217b0aa", 402 | "sha256:d1a3ef11830cc18ef090c4ea55997cb858b02a0925618f818420f9ed3ad33c0c" 403 | ], 404 | "index": "pypi", 405 | "version": "==1.2.1" 406 | }, 407 | "defusedxml": { 408 | "hashes": [ 409 | "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", 410 | "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61" 411 | ], 412 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 413 | "version": "==0.7.1" 414 | }, 415 | "deprecated": { 416 | "hashes": [ 417 | "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d", 418 | "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d" 419 | ], 420 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 421 | "version": "==1.2.13" 422 | }, 423 | "dill": { 424 | "hashes": [ 425 | "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302", 426 | "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86" 427 | ], 428 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", 429 | "version": "==0.3.5.1" 430 | }, 431 | "dnspython": { 432 | "hashes": [ 433 | "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e", 434 | "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f" 435 | ], 436 | "markers": "python_version >= '3.6' and python_version < '4.0'", 437 | "version": "==2.2.1" 438 | }, 439 | "docutils": { 440 | "hashes": [ 441 | "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6", 442 | "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc" 443 | ], 444 | "markers": "python_version >= '3.7'", 445 | "version": "==0.19" 446 | }, 447 | "email-validator": { 448 | "hashes": [ 449 | "sha256:553a66f8be2ec2dea641ae1d3f29017ab89e9d603d4a25cdaac39eefa283d769", 450 | "sha256:816073f2a7cffef786b29928f58ec16cdac42710a53bb18aa94317e3e145ec5c" 451 | ], 452 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 453 | "version": "==1.3.0" 454 | }, 455 | "flask": { 456 | "hashes": [ 457 | "sha256:0fbeb6180d383a9186d0d6ed954e0042ad9f18e0e8de088b2b419d526927d196", 458 | "sha256:c34f04500f2cbbea882b1acb02002ad6fe6b7ffa64a6164577995657f50aed22" 459 | ], 460 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 461 | "version": "==1.1.4" 462 | }, 463 | "flask-appbuilder": { 464 | "hashes": [ 465 | "sha256:7a4c9981bc030f29e4071edd173e9fe8cfcdbe7febefe141160b6783a0cc871a", 466 | "sha256:de53e5da250a3a64865ee0a2ed2f9235117d0206bf6f33bdfe3e3da6334bb0d1" 467 | ], 468 | "markers": "python_version ~= '3.6'", 469 | "version": "==3.4.5" 470 | }, 471 | "flask-babel": { 472 | "hashes": [ 473 | "sha256:e6820a052a8d344e178cdd36dd4bb8aea09b4bda3d5f9fa9f008df2c7f2f5468", 474 | "sha256:f9faf45cdb2e1a32ea2ec14403587d4295108f35017a7821a2b1acb8cfd9257d" 475 | ], 476 | "version": "==2.0.0" 477 | }, 478 | "flask-caching": { 479 | "hashes": [ 480 | "sha256:28af189e97defb9e39b43ebe197b54a58aaee81bdeb759f46d969c26d7aa7810", 481 | "sha256:36592812eec6cba86eca48bcda74eff24bfd6c8eaf6056ca0184474bb78c0dc4" 482 | ], 483 | "markers": "python_version >= '3.7'", 484 | "version": "==1.11.1" 485 | }, 486 | "flask-jwt-extended": { 487 | "hashes": [ 488 | "sha256:bbf4467f41c56cf1fd8a5870d2556f419c572aad2b4085757581c3f9b4d7767a" 489 | ], 490 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", 491 | "version": "==3.25.1" 492 | }, 493 | "flask-login": { 494 | "hashes": [ 495 | "sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec" 496 | ], 497 | "version": "==0.4.1" 498 | }, 499 | "flask-openid": { 500 | "hashes": [ 501 | "sha256:2d4560721b7bf2d014caf5180b3b10b746d221c534d2e63c4469f83af25f9791", 502 | "sha256:539289ed2d19af61ae38d8fe46aec9e4de2b56f9f8b46da0b98c0d387f1d975a" 503 | ], 504 | "markers": "python_version >= '3.0'", 505 | "version": "==1.3.0" 506 | }, 507 | "flask-session": { 508 | "hashes": [ 509 | "sha256:1e3f8a317005db72c831f85d884a5a9d23145f256c730d80b325a3150a22c3db", 510 | "sha256:c9ed54321fa8c4ca0132ffd3369582759eda7252fb4b3bee480e690d1ba41f46" 511 | ], 512 | "version": "==0.4.0" 513 | }, 514 | "flask-sqlalchemy": { 515 | "hashes": [ 516 | "sha256:2bda44b43e7cacb15d4e05ff3cc1f8bc97936cc464623424102bfc2c35e95912", 517 | "sha256:f12c3d4cc5cc7fdcc148b9527ea05671718c3ea45d50c7e732cceb33f574b390" 518 | ], 519 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 520 | "version": "==2.5.1" 521 | }, 522 | "flask-wtf": { 523 | "hashes": [ 524 | "sha256:57b3faf6fe5d6168bda0c36b0df1d05770f8e205e18332d0376ddb954d17aef2", 525 | "sha256:d417e3a0008b5ba583da1763e4db0f55a1269d9dd91dcc3eb3c026d3c5dbd720" 526 | ], 527 | "version": "==0.14.3" 528 | }, 529 | "future": { 530 | "hashes": [ 531 | "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d" 532 | ], 533 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 534 | "version": "==0.18.2" 535 | }, 536 | "graphviz": { 537 | "hashes": [ 538 | "sha256:587c58a223b51611c0cf461132da386edd896a029524ca61a1462b880bf97977", 539 | "sha256:8c58f14adaa3b947daf26c19bc1e98c4e0702cdc31cf99153e6f06904d492bf8" 540 | ], 541 | "markers": "python_version >= '3.7'", 542 | "version": "==0.20.1" 543 | }, 544 | "greenlet": { 545 | "hashes": [ 546 | "sha256:0118817c9341ef2b0f75f5af79ac377e4da6ff637e5ee4ac91802c0e379dadb4", 547 | "sha256:048d2bed76c2aa6de7af500ae0ea51dd2267aec0e0f2a436981159053d0bc7cc", 548 | "sha256:07c58e169bbe1e87b8bbf15a5c1b779a7616df9fd3e61cadc9d691740015b4f8", 549 | "sha256:095a980288fe05adf3d002fbb180c99bdcf0f930e220aa66fcd56e7914a38202", 550 | "sha256:0b181e9aa6cb2f5ec0cacc8cee6e5a3093416c841ba32c185c30c160487f0380", 551 | "sha256:1626185d938d7381631e48e6f7713e8d4b964be246073e1a1d15c2f061ac9f08", 552 | "sha256:184416e481295832350a4bf731ba619a92f5689bf5d0fa4341e98b98b1265bd7", 553 | "sha256:1dd51d2650e70c6c4af37f454737bf4a11e568945b27f74b471e8e2a9fd21268", 554 | "sha256:1ec2779774d8e42ed0440cf8bc55540175187e8e934f2be25199bf4ed948cd9e", 555 | "sha256:2cf45e339cabea16c07586306a31cfcc5a3b5e1626d365714d283732afed6809", 556 | "sha256:2fb0aa7f6996879551fd67461d5d3ab0c3c0245da98be90c89fcb7a18d437403", 557 | "sha256:44b4817c34c9272c65550b788913620f1fdc80362b209bc9d7dd2f40d8793080", 558 | "sha256:466ce0928e33421ee84ae04c4ac6f253a3a3e6b8d600a79bd43fd4403e0a7a76", 559 | "sha256:4f166b4aca8d7d489e82d74627a7069ab34211ef5ebb57c300ec4b9337b60fc0", 560 | "sha256:510c3b15587afce9800198b4b142202b323bf4b4b5f9d6c79cb9a35e5e3c30d2", 561 | "sha256:5b756e6730ea59b2745072e28ad27f4c837084688e6a6b3633c8b1e509e6ae0e", 562 | "sha256:5fbe1ab72b998ca77ceabbae63a9b2e2dc2d963f4299b9b278252ddba142d3f1", 563 | "sha256:6200a11f003ec26815f7e3d2ded01b43a3810be3528dd760d2f1fa777490c3cd", 564 | "sha256:65ad1a7a463a2a6f863661329a944a5802c7129f7ad33583dcc11069c17e622c", 565 | "sha256:694ffa7144fa5cc526c8f4512665003a39fa09ef00d19bbca5c8d3406db72fbe", 566 | "sha256:6f5d4b2280ceea76c55c893827961ed0a6eadd5a584a7c4e6e6dd7bc10dfdd96", 567 | "sha256:7532a46505470be30cbf1dbadb20379fb481244f1ca54207d7df3bf0bbab6a20", 568 | "sha256:76a53bfa10b367ee734b95988bd82a9a5f0038a25030f9f23bbbc005010ca600", 569 | "sha256:77e41db75f9958f2083e03e9dd39da12247b3430c92267df3af77c83d8ff9eed", 570 | "sha256:7a43bbfa9b6cfdfaeefbd91038dde65ea2c421dc387ed171613df340650874f2", 571 | "sha256:7b41d19c0cfe5c259fe6c539fd75051cd39a5d33d05482f885faf43f7f5e7d26", 572 | "sha256:7c5227963409551ae4a6938beb70d56bf1918c554a287d3da6853526212fbe0a", 573 | "sha256:870a48007872d12e95a996fca3c03a64290d3ea2e61076aa35d3b253cf34cd32", 574 | "sha256:88b04e12c9b041a1e0bcb886fec709c488192638a9a7a3677513ac6ba81d8e79", 575 | "sha256:8c287ae7ac921dfde88b1c125bd9590b7ec3c900c2d3db5197f1286e144e712b", 576 | "sha256:903fa5716b8fbb21019268b44f73f3748c41d1a30d71b4a49c84b642c2fed5fa", 577 | "sha256:9537e4baf0db67f382eb29255a03154fcd4984638303ff9baaa738b10371fa57", 578 | "sha256:9951dcbd37850da32b2cb6e391f621c1ee456191c6ae5528af4a34afe357c30e", 579 | "sha256:9b2f7d0408ddeb8ea1fd43d3db79a8cefaccadd2a812f021333b338ed6b10aba", 580 | "sha256:9c88e134d51d5e82315a7c32b914a58751b7353eb5268dbd02eabf020b4c4700", 581 | "sha256:9fae214f6c43cd47f7bef98c56919b9222481e833be2915f6857a1e9e8a15318", 582 | "sha256:a3a669f11289a8995d24fbfc0e63f8289dd03c9aaa0cc8f1eab31d18ca61a382", 583 | "sha256:aa741c1a8a8cc25eb3a3a01a62bdb5095a773d8c6a86470bde7f607a447e7905", 584 | "sha256:b0877a9a2129a2c56a2eae2da016743db7d9d6a05d5e1c198f1b7808c602a30e", 585 | "sha256:bcb6c6dd1d6be6d38d6db283747d07fda089ff8c559a835236560a4410340455", 586 | "sha256:caff52cb5cd7626872d9696aee5b794abe172804beb7db52eed1fd5824b63910", 587 | "sha256:cbc1eb55342cbac8f7ec159088d54e2cfdd5ddf61c87b8bbe682d113789331b2", 588 | "sha256:cd16a89efe3a003029c87ff19e9fba635864e064da646bc749fc1908a4af18f3", 589 | "sha256:ce5b64dfe8d0cca407d88b0ee619d80d4215a2612c1af8c98a92180e7109f4b5", 590 | "sha256:d58a5a71c4c37354f9e0c24c9c8321f0185f6945ef027460b809f4bb474bfe41", 591 | "sha256:db41f3845eb579b544c962864cce2c2a0257fe30f0f1e18e51b1e8cbb4e0ac6d", 592 | "sha256:db5b25265010a1b3dca6a174a443a0ed4c4ab12d5e2883a11c97d6e6d59b12f9", 593 | "sha256:dd0404d154084a371e6d2bafc787201612a1359c2dee688ae334f9118aa0bf47", 594 | "sha256:de431765bd5fe62119e0bc6bc6e7b17ac53017ae1782acf88fcf6b7eae475a49", 595 | "sha256:df02fdec0c533301497acb0bc0f27f479a3a63dcdc3a099ae33a902857f07477", 596 | "sha256:e8533f5111704d75de3139bf0b8136d3a6c1642c55c067866fa0a51c2155ee33", 597 | "sha256:f2f908239b7098799b8845e5936c2ccb91d8c2323be02e82f8dcb4a80dcf4a25", 598 | "sha256:f8bfd36f368efe0ab2a6aa3db7f14598aac454b06849fb633b762ddbede1db90", 599 | "sha256:ffe73f9e7aea404722058405ff24041e59d31ca23d1da0895af48050a07b6932" 600 | ], 601 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 602 | "version": "==1.1.3" 603 | }, 604 | "gunicorn": { 605 | "hashes": [ 606 | "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", 607 | "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" 608 | ], 609 | "markers": "python_version >= '3.5'", 610 | "version": "==20.1.0" 611 | }, 612 | "h11": { 613 | "hashes": [ 614 | "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", 615 | "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" 616 | ], 617 | "markers": "python_version >= '3.6'", 618 | "version": "==0.12.0" 619 | }, 620 | "hologram": { 621 | "hashes": [ 622 | "sha256:48ca81ed47da1c604b2d3b951424b600eb8a5785b00513e3b8e3ae8101f90145", 623 | "sha256:79b3d04df84d5a9d09c2e669ec5bcc50b1713ec79f4683cfdea85583b41e46f0" 624 | ], 625 | "version": "==0.0.15" 626 | }, 627 | "httpcore": { 628 | "hashes": [ 629 | "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6", 630 | "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b" 631 | ], 632 | "markers": "python_version >= '3.7'", 633 | "version": "==0.15.0" 634 | }, 635 | "httpx": { 636 | "hashes": [ 637 | "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b", 638 | "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef" 639 | ], 640 | "markers": "python_version >= '3.7'", 641 | "version": "==0.23.0" 642 | }, 643 | "idna": { 644 | "hashes": [ 645 | "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", 646 | "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" 647 | ], 648 | "markers": "python_version >= '3.5'", 649 | "version": "==3.4" 650 | }, 651 | "importlib-metadata": { 652 | "hashes": [ 653 | "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab", 654 | "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43" 655 | ], 656 | "markers": "python_version < '3.10'", 657 | "version": "==5.0.0" 658 | }, 659 | "inflection": { 660 | "hashes": [ 661 | "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417", 662 | "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2" 663 | ], 664 | "markers": "python_version >= '3.5'", 665 | "version": "==0.5.1" 666 | }, 667 | "iniconfig": { 668 | "hashes": [ 669 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 670 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 671 | ], 672 | "version": "==1.1.1" 673 | }, 674 | "isodate": { 675 | "hashes": [ 676 | "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", 677 | "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9" 678 | ], 679 | "version": "==0.6.1" 680 | }, 681 | "itsdangerous": { 682 | "hashes": [ 683 | "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", 684 | "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" 685 | ], 686 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 687 | "version": "==1.1.0" 688 | }, 689 | "jinja2": { 690 | "hashes": [ 691 | "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", 692 | "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" 693 | ], 694 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 695 | "version": "==2.11.3" 696 | }, 697 | "jmespath": { 698 | "hashes": [ 699 | "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", 700 | "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe" 701 | ], 702 | "markers": "python_version >= '3.7'", 703 | "version": "==1.0.1" 704 | }, 705 | "jsonschema": { 706 | "hashes": [ 707 | "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", 708 | "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" 709 | ], 710 | "version": "==3.2.0" 711 | }, 712 | "lazy-object-proxy": { 713 | "hashes": [ 714 | "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", 715 | "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", 716 | "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", 717 | "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", 718 | "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", 719 | "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", 720 | "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", 721 | "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", 722 | "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", 723 | "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", 724 | "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", 725 | "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", 726 | "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", 727 | "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", 728 | "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", 729 | "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", 730 | "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", 731 | "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", 732 | "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", 733 | "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", 734 | "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", 735 | "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", 736 | "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", 737 | "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", 738 | "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", 739 | "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", 740 | "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", 741 | "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", 742 | "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", 743 | "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", 744 | "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", 745 | "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", 746 | "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", 747 | "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", 748 | "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", 749 | "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", 750 | "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" 751 | ], 752 | "markers": "python_version >= '3.6'", 753 | "version": "==1.7.1" 754 | }, 755 | "leather": { 756 | "hashes": [ 757 | "sha256:5e741daee96e9f1e9e06081b8c8a10c4ac199301a0564cdd99b09df15b4603d2", 758 | "sha256:b43e21c8fa46b2679de8449f4d953c06418666dc058ce41055ee8a8d3bb40918" 759 | ], 760 | "version": "==0.3.4" 761 | }, 762 | "lockfile": { 763 | "hashes": [ 764 | "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799", 765 | "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa" 766 | ], 767 | "version": "==0.12.2" 768 | }, 769 | "logbook": { 770 | "hashes": [ 771 | "sha256:0cf2cdbfb65a03b5987d19109dacad13417809dcf697f66e1a7084fb21744ea9", 772 | "sha256:2dc85f1510533fddb481e97677bb7bca913560862734c0b3b289bfed04f78c92", 773 | "sha256:56ee54c11df3377314cedcd6507638f015b4b88c0238c2e01b5eb44fd3a6ad1b", 774 | "sha256:66f454ada0f56eae43066f604a222b09893f98c1adc18df169710761b8f32fe8", 775 | "sha256:7c533eb728b3d220b1b5414ba4635292d149d79f74f6973b4aa744c850ca944a", 776 | "sha256:8f76a2e7b1f72595f753228732f81ce342caf03babc3fed6bbdcf366f2f20f18", 777 | "sha256:94e2e11ff3c2304b0d09a36c6208e5ae756eb948b210e5cbd63cd8d27f911542", 778 | "sha256:97fee1bd9605f76335b169430ed65e15e457a844b2121bd1d90a08cf7e30aba0", 779 | "sha256:e18f7422214b1cf0240c56f884fd9c9b4ff9d0da2eabca9abccba56df7222f66" 780 | ], 781 | "version": "==1.5.3" 782 | }, 783 | "mako": { 784 | "hashes": [ 785 | "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd", 786 | "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f" 787 | ], 788 | "markers": "python_version >= '3.7'", 789 | "version": "==1.2.3" 790 | }, 791 | "markdown": { 792 | "hashes": [ 793 | "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186", 794 | "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff" 795 | ], 796 | "markers": "python_version >= '3.7'", 797 | "version": "==3.4.1" 798 | }, 799 | "markupsafe": { 800 | "hashes": [ 801 | "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", 802 | "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", 803 | "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", 804 | "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", 805 | "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", 806 | "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", 807 | "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", 808 | "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", 809 | "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", 810 | "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", 811 | "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", 812 | "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", 813 | "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", 814 | "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", 815 | "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", 816 | "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", 817 | "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", 818 | "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", 819 | "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", 820 | "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", 821 | "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", 822 | "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", 823 | "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", 824 | "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", 825 | "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", 826 | "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", 827 | "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", 828 | "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", 829 | "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", 830 | "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", 831 | "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", 832 | "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", 833 | "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", 834 | "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", 835 | "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", 836 | "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", 837 | "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", 838 | "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", 839 | "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", 840 | "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", 841 | "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", 842 | "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", 843 | "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", 844 | "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", 845 | "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", 846 | "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", 847 | "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", 848 | "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", 849 | "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", 850 | "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", 851 | "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", 852 | "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", 853 | "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", 854 | "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", 855 | "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", 856 | "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", 857 | "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", 858 | "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", 859 | "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", 860 | "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", 861 | "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", 862 | "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", 863 | "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", 864 | "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", 865 | "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", 866 | "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", 867 | "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", 868 | "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", 869 | "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" 870 | ], 871 | "markers": "python_version >= '3.6'", 872 | "version": "==2.0.1" 873 | }, 874 | "marshmallow": { 875 | "hashes": [ 876 | "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104", 877 | "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7" 878 | ], 879 | "markers": "python_version >= '3.7'", 880 | "version": "==3.18.0" 881 | }, 882 | "marshmallow-enum": { 883 | "hashes": [ 884 | "sha256:38e697e11f45a8e64b4a1e664000897c659b60aa57bfa18d44e226a9920b6e58", 885 | "sha256:57161ab3dbfde4f57adeb12090f39592e992b9c86d206d02f6bd03ebec60f072" 886 | ], 887 | "version": "==1.5.1" 888 | }, 889 | "marshmallow-oneofschema": { 890 | "hashes": [ 891 | "sha256:62cd2099b29188c92493c2940ee79d1bf2f2619a71721664e5a98ec2faa58237", 892 | "sha256:bd29410a9f2f7457a2b428286e2a80ef76b8ddc3701527dc1f935a88914b02f2" 893 | ], 894 | "markers": "python_version >= '3.6'", 895 | "version": "==3.0.1" 896 | }, 897 | "marshmallow-sqlalchemy": { 898 | "hashes": [ 899 | "sha256:ba7493eeb8669a3bf00d8f906b657feaa87a740ae9e4ecf829cfd6ddf763d276", 900 | "sha256:d8525f74de51554b5c8491effe036f60629a426229befa33ff614c8569a16a73" 901 | ], 902 | "markers": "python_version >= '3.6'", 903 | "version": "==0.26.1" 904 | }, 905 | "mashumaro": { 906 | "hashes": [ 907 | "sha256:343b6e2d3e432e31973688c4c8821dcd6ef41fd33264b992afc4aecbfd155f18", 908 | "sha256:f616df410d82936b8bb2b4d32af570556685d77f49acf4228134b50230a69799" 909 | ], 910 | "markers": "python_version >= '3.6'", 911 | "version": "==2.9" 912 | }, 913 | "minimal-snowplow-tracker": { 914 | "hashes": [ 915 | "sha256:acabf7572db0e7f5cbf6983d495eef54081f71be392330eb3aadb9ccb39daaa4" 916 | ], 917 | "version": "==0.0.2" 918 | }, 919 | "msgpack": { 920 | "hashes": [ 921 | "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467", 922 | "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae", 923 | "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92", 924 | "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef", 925 | "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624", 926 | "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227", 927 | "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88", 928 | "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9", 929 | "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8", 930 | "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd", 931 | "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6", 932 | "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55", 933 | "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e", 934 | "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2", 935 | "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44", 936 | "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6", 937 | "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9", 938 | "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab", 939 | "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae", 940 | "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa", 941 | "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9", 942 | "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e", 943 | "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250", 944 | "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce", 945 | "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075", 946 | "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236", 947 | "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae", 948 | "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e", 949 | "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f", 950 | "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08", 951 | "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6", 952 | "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d", 953 | "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43", 954 | "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1", 955 | "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6", 956 | "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0", 957 | "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c", 958 | "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff", 959 | "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db", 960 | "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243", 961 | "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661", 962 | "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba", 963 | "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e", 964 | "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb", 965 | "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52", 966 | "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6", 967 | "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1", 968 | "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f", 969 | "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da", 970 | "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f", 971 | "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c", 972 | "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8" 973 | ], 974 | "version": "==1.0.4" 975 | }, 976 | "mypy": { 977 | "hashes": [ 978 | "sha256:1021c241e8b6e1ca5a47e4d52601274ac078a89845cfde66c6d5f769819ffa1d", 979 | "sha256:14d53cdd4cf93765aa747a7399f0961a365bcddf7855d9cef6306fa41de01c24", 980 | "sha256:175f292f649a3af7082fe36620369ffc4661a71005aa9f8297ea473df5772046", 981 | "sha256:26ae64555d480ad4b32a267d10cab7aec92ff44de35a7cd95b2b7cb8e64ebe3e", 982 | "sha256:41fd1cf9bc0e1c19b9af13a6580ccb66c381a5ee2cf63ee5ebab747a4badeba3", 983 | "sha256:5085e6f442003fa915aeb0a46d4da58128da69325d8213b4b35cc7054090aed5", 984 | "sha256:58f27ebafe726a8e5ccb58d896451dd9a662a511a3188ff6a8a6a919142ecc20", 985 | "sha256:6389af3e204975d6658de4fb8ac16f58c14e1bacc6142fee86d1b5b26aa52bda", 986 | "sha256:724d36be56444f569c20a629d1d4ee0cb0ad666078d59bb84f8f887952511ca1", 987 | "sha256:75838c649290d83a2b83a88288c1eb60fe7a05b36d46cbea9d22efc790002146", 988 | "sha256:7b35ce03a289480d6544aac85fa3674f493f323d80ea7226410ed065cd46f206", 989 | "sha256:85f7a343542dc8b1ed0a888cdd34dca56462654ef23aa673907305b260b3d746", 990 | "sha256:86ebe67adf4d021b28c3f547da6aa2cce660b57f0432617af2cca932d4d378a6", 991 | "sha256:8ee8c2472e96beb1045e9081de8e92f295b89ac10c4109afdf3a23ad6e644f3e", 992 | "sha256:91781eff1f3f2607519c8b0e8518aad8498af1419e8442d5d0afb108059881fc", 993 | "sha256:a692a8e7d07abe5f4b2dd32d731812a0175626a90a223d4b58f10f458747dd8a", 994 | "sha256:a705a93670c8b74769496280d2fe6cd59961506c64f329bb179970ff1d24f9f8", 995 | "sha256:c6e564f035d25c99fd2b863e13049744d96bd1947e3d3d2f16f5828864506763", 996 | "sha256:cebca7fd333f90b61b3ef7f217ff75ce2e287482206ef4a8b18f32b49927b1a2", 997 | "sha256:d6af646bd46f10d53834a8e8983e130e47d8ab2d4b7a97363e35b24e1d588947", 998 | "sha256:e7aeaa763c7ab86d5b66ff27f68493d672e44c8099af636d433a7f3fa5596d40", 999 | "sha256:eaa97b9ddd1dd9901a22a879491dbb951b5dec75c3b90032e2baa7336777363b", 1000 | "sha256:eb7a068e503be3543c4bd329c994103874fa543c1727ba5288393c21d912d795", 1001 | "sha256:f793e3dd95e166b66d50e7b63e69e58e88643d80a3dcc3bcd81368e0478b089c" 1002 | ], 1003 | "index": "pypi", 1004 | "version": "==0.982" 1005 | }, 1006 | "mypy-extensions": { 1007 | "hashes": [ 1008 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 1009 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 1010 | ], 1011 | "version": "==0.4.3" 1012 | }, 1013 | "networkx": { 1014 | "hashes": [ 1015 | "sha256:15cdf7f7c157637107ea690cabbc488018f8256fa28242aed0fb24c93c03a06d", 1016 | "sha256:815383fd52ece0a7024b5fd8408cc13a389ea350cd912178b82eed8b96f82cd3" 1017 | ], 1018 | "markers": "python_version >= '3.8'", 1019 | "version": "==2.8.7" 1020 | }, 1021 | "numpy": { 1022 | "hashes": [ 1023 | "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089", 1024 | "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164", 1025 | "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440", 1026 | "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18", 1027 | "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c", 1028 | "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d", 1029 | "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c", 1030 | "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd", 1031 | "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036", 1032 | "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd", 1033 | "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c", 1034 | "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4", 1035 | "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f", 1036 | "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d", 1037 | "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584", 1038 | "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8", 1039 | "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a", 1040 | "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460", 1041 | "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6", 1042 | "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411", 1043 | "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1", 1044 | "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee", 1045 | "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7", 1046 | "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14", 1047 | "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6", 1048 | "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e", 1049 | "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85", 1050 | "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54" 1051 | ], 1052 | "index": "pypi", 1053 | "version": "==1.23.3" 1054 | }, 1055 | "packaging": { 1056 | "hashes": [ 1057 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 1058 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 1059 | ], 1060 | "markers": "python_version >= '3.6'", 1061 | "version": "==21.3" 1062 | }, 1063 | "pandas": { 1064 | "hashes": [ 1065 | "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484", 1066 | "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc", 1067 | "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8", 1068 | "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b", 1069 | "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989", 1070 | "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8", 1071 | "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488", 1072 | "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f", 1073 | "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977", 1074 | "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f", 1075 | "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761", 1076 | "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c", 1077 | "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38", 1078 | "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515", 1079 | "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a", 1080 | "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a", 1081 | "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec", 1082 | "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3", 1083 | "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac", 1084 | "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b", 1085 | "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be", 1086 | "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d", 1087 | "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9", 1088 | "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8", 1089 | "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060", 1090 | "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009", 1091 | "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9" 1092 | ], 1093 | "index": "pypi", 1094 | "version": "==1.5.0" 1095 | }, 1096 | "parsedatetime": { 1097 | "hashes": [ 1098 | "sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b", 1099 | "sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094" 1100 | ], 1101 | "version": "==2.4" 1102 | }, 1103 | "pathspec": { 1104 | "hashes": [ 1105 | "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", 1106 | "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" 1107 | ], 1108 | "version": "==0.9.0" 1109 | }, 1110 | "pendulum": { 1111 | "hashes": [ 1112 | "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394", 1113 | "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b", 1114 | "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a", 1115 | "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087", 1116 | "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739", 1117 | "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269", 1118 | "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0", 1119 | "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5", 1120 | "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be", 1121 | "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7", 1122 | "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3", 1123 | "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207", 1124 | "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe", 1125 | "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360", 1126 | "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0", 1127 | "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b", 1128 | "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052", 1129 | "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002", 1130 | "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116", 1131 | "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db", 1132 | "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b" 1133 | ], 1134 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1135 | "version": "==2.1.2" 1136 | }, 1137 | "pluggy": { 1138 | "hashes": [ 1139 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 1140 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 1141 | ], 1142 | "markers": "python_version >= '3.6'", 1143 | "version": "==1.0.0" 1144 | }, 1145 | "prison": { 1146 | "hashes": [ 1147 | "sha256:e6cd724044afcb1a8a69340cad2f1e3151a5839fd3a8027fd1357571e797c599", 1148 | "sha256:f90bab63fca497aa0819a852f64fb21a4e181ed9f6114deaa5dc04001a7555c5" 1149 | ], 1150 | "version": "==0.2.1" 1151 | }, 1152 | "psutil": { 1153 | "hashes": [ 1154 | "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", 1155 | "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", 1156 | "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", 1157 | "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", 1158 | "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", 1159 | "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", 1160 | "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", 1161 | "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", 1162 | "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", 1163 | "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", 1164 | "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", 1165 | "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", 1166 | "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", 1167 | "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", 1168 | "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", 1169 | "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", 1170 | "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", 1171 | "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", 1172 | "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", 1173 | "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", 1174 | "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", 1175 | "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", 1176 | "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", 1177 | "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", 1178 | "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", 1179 | "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", 1180 | "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", 1181 | "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", 1182 | "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", 1183 | "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", 1184 | "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", 1185 | "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" 1186 | ], 1187 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1188 | "version": "==5.9.2" 1189 | }, 1190 | "psycopg2": { 1191 | "hashes": [ 1192 | "sha256:07b90a24d5056687781ddaef0ea172fd951f2f7293f6ffdd03d4f5077801f426", 1193 | "sha256:1da77c061bdaab450581458932ae5e469cc6e36e0d62f988376e9f513f11cb5c", 1194 | "sha256:46361c054df612c3cc813fdb343733d56543fb93565cff0f8ace422e4da06acb", 1195 | "sha256:839f9ea8f6098e39966d97fcb8d08548fbc57c523a1e27a1f0609addf40f777c", 1196 | "sha256:849bd868ae3369932127f0771c08d1109b254f08d48dc42493c3d1b87cb2d308", 1197 | "sha256:8de6a9fc5f42fa52f559e65120dcd7502394692490c98fed1221acf0819d7797", 1198 | "sha256:a11946bad3557ca254f17357d5a4ed63bdca45163e7a7d2bfb8e695df069cc3a", 1199 | "sha256:aa184d551a767ad25df3b8d22a0a62ef2962e0e374c04f6cbd1204947f540d61", 1200 | "sha256:aafa96f2da0071d6dd0cbb7633406d99f414b40ab0f918c9d9af7df928a1accb", 1201 | "sha256:c7fa041b4acb913f6968fce10169105af5200f296028251d817ab37847c30184", 1202 | "sha256:d529926254e093a1b669f692a3aa50069bc71faf5b0ecd91686a78f62767d52f" 1203 | ], 1204 | "index": "pypi", 1205 | "version": "==2.9.4" 1206 | }, 1207 | "psycopg2-binary": { 1208 | "hashes": [ 1209 | "sha256:02cde837df012fa5d579b9cf4bc8e1feb460f38d61f7a4ab4a919d55a9f6eeef", 1210 | "sha256:044b6ab68613de7ea1e63856627deea091bfea09dea5ab4f050b13250fd18cab", 1211 | "sha256:0a9465f0aa36480c8e7614991cbe8ca8aa16b0517c5398a49648ce345e446c19", 1212 | "sha256:0d8e0c9eec79fe1ae66691e06e3cc714da6fbd77981209bf32fa823c03dbaff8", 1213 | "sha256:0eae72190be519bf2629062eab7ac8d4ceec5bd132953cefa1596584d86964fe", 1214 | "sha256:15e0ac0ed8a85f6049e836e95ddee627766561c85be8d23f4b3edb6ddbaa7310", 1215 | "sha256:161dc52a617f0bb610a87d391cb2e77fe65b89ebfbd752f4f3217dde701ea196", 1216 | "sha256:181ac372a5a5308b4076933601a9b5f0cd139b389b0aa5e164786a2abbcdb978", 1217 | "sha256:1c22c59ab7d9dc110d409445f111f58556bf699b0548f3fc5176684a29c629c4", 1218 | "sha256:226f11be577b70a57f4910c0ee28591d4d9fcb3d455e966267179156ae2e0c41", 1219 | "sha256:24d627ed69e754c48dd142a914124858c600b4108c92546eb0ba822e63c0c6e2", 1220 | "sha256:2535f44b00f26f6af0e949c825e6aecb9adcb56c965c17af5b97137fb69f00c0", 1221 | "sha256:25e0517ad7ee3c5c3c69dbe3c1d95504c811e42f452b39a3505d0763b1f6caa0", 1222 | "sha256:2903bf90b1e6bfc9bbfc94a1db0b50ffa9830a0ca4c042fbc38d93890c02ce08", 1223 | "sha256:2f1ded23d17af0d738e7e78087f0b88a53228887845b1989b03af4dfd3fef703", 1224 | "sha256:30200b07779446760813eef06098ec6d084131e4365b4e023eb43100de758b11", 1225 | "sha256:33ac8b4754e6b6b21f3ee180da169d8526d91aee9408ec1fc573c16ab32b0207", 1226 | "sha256:34fd249275faa782c3a2016e86ac2330636ac58d731a1580e7d686e3976b9536", 1227 | "sha256:44f5dc9b4384bafca8429759ce76c8960ffc2b583fcad9e5dfb3e5f4894269e4", 1228 | "sha256:451550e0bb5889bbabbf92575a6d6eafced941cc28c86be6ae4667f81bf32d67", 1229 | "sha256:52383e932e6de5595963f9178cf2af7b9e1f3daacf5135b9c0e21aabbc5bf7c4", 1230 | "sha256:55137faec669c4277c5687c6ce7c1fbc4dece0e2f14256ee808f4a652f0a2170", 1231 | "sha256:576b9dfbcd154a0e8b5d9dae6316d037450e64a3b31df87dec71d88e2a2d5e5f", 1232 | "sha256:59a3010d566a48b919490a982f6807f68842686941dc12d568e129d9cd7703d6", 1233 | "sha256:61c6a258469c66412ae8358a0501df6ccb3bb48aa9c43b56624571ff9767f91d", 1234 | "sha256:63edc507f8cbfbb5903adb75bad8a99f9798981c854df9119dbebab2ec3ee0e1", 1235 | "sha256:65d5f4e70a2d3fbaa1349236968792611088f3f2dccead36c1626e1d183cc327", 1236 | "sha256:6a1618260a112a9c93504511f0b6254b4402a8c41b7130dc6d4c9e39aff3aa0c", 1237 | "sha256:704f1fcdc5b606b70563ea696c69bda90caee3a2f45ffc9cee60a901b394a79f", 1238 | "sha256:7751b11cd7f6b952b4b5ec5b93b5be9ce20faba786c18c25c354f5d8717a173c", 1239 | "sha256:7ad9d032dc1a31a86ca7b059f43554a049a2bfda8fe32d1492ad25f6686aff03", 1240 | "sha256:7b01d07006a0ac2216921b69a220b9f0974345d0b1b36efaeabdc7550b1cc4f8", 1241 | "sha256:7b47643c45e7619788c081d42e1d9d98c7c8a4933010a9967d097cc3c4c29f41", 1242 | "sha256:80ed219ce6cb21a5b53ead0edf5b56b6d23de4cb95389ac606f47670474f4816", 1243 | "sha256:82df4a8600999c4c0cb7d6614df1bbdb3c74732f63e79f78487893ffbed3d083", 1244 | "sha256:8660112e9127a019969a23c878e1b4a419e8a6427f9a9050c19830f152628c8a", 1245 | "sha256:89a86c2b35460700d04b4d6461153ab39ee85af5a5385acac9563a8310e6320a", 1246 | "sha256:8d7bc25729bb6d96b44f49ad78fde0e27a1a867cb205322b7e5f5b49e04d6f1f", 1247 | "sha256:97e4f3d9b17d12e7c00cb1c29c0040044135cd5146838da4274615dbe0baae78", 1248 | "sha256:a431deb6ffdfa551f7400b3a94fa4b964837e67f49e3c37aa26d90dc75970816", 1249 | "sha256:a6a2d3d75d8698dee492f4af7ad07606d0734e581edf9e2ce2f74b6fce90f42e", 1250 | "sha256:ae5b41dbf7731b838021923edfbe3b5ccdec84d92d5795f5229c0d08d32509d9", 1251 | "sha256:aff258af03dda9a990960a53759d10c3a9b936837c71fe2f3b581acd356b9121", 1252 | "sha256:b216a15e13f6e763db40ac3beb74b588650bc030d10a78fde182b88d273b82b5", 1253 | "sha256:b23b25b1243576b952689966205ef7d4285688068b966a1ca0e620bcb390d483", 1254 | "sha256:b896637091cde69d170a89253dde9aee814b25ca204b7e213fd0a6462e666638", 1255 | "sha256:d5f27b1d1b56470385faa2b2636fcb823e7ac5b5b734e0aa76b14637c66eb3b7", 1256 | "sha256:d6ba33f39436191ece7ea2b3d0b4dff00af71acd5c6e6f1d6b7563aa7286e9f2", 1257 | "sha256:d6c5e1df6f427d7a82606cf8f07cf3ba9fb3f366804b01e65f1f00f8df6b54f1", 1258 | "sha256:e02f77b620ad6b36564fe41980865436912e21a3b1138cdde175cf24afde1bc5", 1259 | "sha256:e72491d72870c3cb2f0d6f4174485533caec0e9ed7e717e2859b7cc7ff2ae1c4", 1260 | "sha256:ea8d5cd689fa7225d81ae0a049ba03e0165f4ed9ca083b19a405be9ad0b36845", 1261 | "sha256:eb5341fc7c53fdd95ac2415be77b1de854ab266488cff71174ebb007baf0e675", 1262 | "sha256:edf0a66ce9517365c7dcfed597894d8dd1f27b59e550b77a089054101435213b", 1263 | "sha256:f225784812b2b57d340f2eb0d2cebef989dcc82c288f5553e28ee9767c7c8344", 1264 | "sha256:f5fbb3b325c65010e04af206a9243e2df8606736c510c7f268aca6a93e5294a9", 1265 | "sha256:f78cafa25731e0b5aa16fe20bea1abf643d4e853f6bfb8a64421b06b878e2b88", 1266 | "sha256:fb639a0e65dce4a9cccbcbdd8ddd0c8c6ab10bca317b827a5c52ac3c3a4ad60a", 1267 | "sha256:ffb2f288f577a748cc23c65a818290755a4c2da1f87a40d7055b61a096d31e20" 1268 | ], 1269 | "markers": "python_version >= '3.6'", 1270 | "version": "==2.9.4" 1271 | }, 1272 | "py": { 1273 | "hashes": [ 1274 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 1275 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 1276 | ], 1277 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1278 | "version": "==1.11.0" 1279 | }, 1280 | "pycparser": { 1281 | "hashes": [ 1282 | "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", 1283 | "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" 1284 | ], 1285 | "version": "==2.21" 1286 | }, 1287 | "pydantic": { 1288 | "hashes": [ 1289 | "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42", 1290 | "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624", 1291 | "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e", 1292 | "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559", 1293 | "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709", 1294 | "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9", 1295 | "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d", 1296 | "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52", 1297 | "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda", 1298 | "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912", 1299 | "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c", 1300 | "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525", 1301 | "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe", 1302 | "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41", 1303 | "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b", 1304 | "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283", 1305 | "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965", 1306 | "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c", 1307 | "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410", 1308 | "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5", 1309 | "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116", 1310 | "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98", 1311 | "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f", 1312 | "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644", 1313 | "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13", 1314 | "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd", 1315 | "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254", 1316 | "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6", 1317 | "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488", 1318 | "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5", 1319 | "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c", 1320 | "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1", 1321 | "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a", 1322 | "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2", 1323 | "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d", 1324 | "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236" 1325 | ], 1326 | "index": "pypi", 1327 | "version": "==1.10.2" 1328 | }, 1329 | "pygments": { 1330 | "hashes": [ 1331 | "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", 1332 | "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" 1333 | ], 1334 | "markers": "python_version >= '3.6'", 1335 | "version": "==2.13.0" 1336 | }, 1337 | "pyjwt": { 1338 | "hashes": [ 1339 | "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e", 1340 | "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96" 1341 | ], 1342 | "version": "==1.7.1" 1343 | }, 1344 | "pyparsing": { 1345 | "hashes": [ 1346 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 1347 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 1348 | ], 1349 | "markers": "python_full_version >= '3.6.8'", 1350 | "version": "==3.0.9" 1351 | }, 1352 | "pyrsistent": { 1353 | "hashes": [ 1354 | "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c", 1355 | "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc", 1356 | "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e", 1357 | "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26", 1358 | "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec", 1359 | "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286", 1360 | "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045", 1361 | "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec", 1362 | "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8", 1363 | "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c", 1364 | "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca", 1365 | "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22", 1366 | "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a", 1367 | "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96", 1368 | "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc", 1369 | "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1", 1370 | "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07", 1371 | "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6", 1372 | "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b", 1373 | "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5", 1374 | "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6" 1375 | ], 1376 | "markers": "python_version >= '3.7'", 1377 | "version": "==0.18.1" 1378 | }, 1379 | "pytest": { 1380 | "hashes": [ 1381 | "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", 1382 | "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" 1383 | ], 1384 | "index": "pypi", 1385 | "version": "==7.1.3" 1386 | }, 1387 | "python-daemon": { 1388 | "hashes": [ 1389 | "sha256:15c2c5e2cef563e0a5f98d542b77ba59337380b472975d2b2fd6b8c4d5cf46ca", 1390 | "sha256:4e3bf67784c78aaa55ec001a2f832b464a54c5f9c89c11b311e2416a8c247431" 1391 | ], 1392 | "version": "==2.3.1" 1393 | }, 1394 | "python-dateutil": { 1395 | "hashes": [ 1396 | "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", 1397 | "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" 1398 | ], 1399 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1400 | "version": "==2.8.2" 1401 | }, 1402 | "python-dotenv": { 1403 | "hashes": [ 1404 | "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5", 1405 | "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045" 1406 | ], 1407 | "index": "pypi", 1408 | "version": "==0.21.0" 1409 | }, 1410 | "python-nvd3": { 1411 | "hashes": [ 1412 | "sha256:fbd75ff47e0ef255b4aa4f3a8b10dc8b4024aa5a9a7abed5b2406bd3cb817715" 1413 | ], 1414 | "version": "==0.15.0" 1415 | }, 1416 | "python-slugify": { 1417 | "hashes": [ 1418 | "sha256:272d106cb31ab99b3496ba085e3fea0e9e76dcde967b5e9992500d1f785ce4e1", 1419 | "sha256:7b2c274c308b62f4269a9ba701aa69a797e9bca41aeee5b3a9e79e36b6656927" 1420 | ], 1421 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 1422 | "version": "==6.1.2" 1423 | }, 1424 | "python3-openid": { 1425 | "hashes": [ 1426 | "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf", 1427 | "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b" 1428 | ], 1429 | "version": "==3.2.0" 1430 | }, 1431 | "pytimeparse": { 1432 | "hashes": [ 1433 | "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", 1434 | "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a" 1435 | ], 1436 | "version": "==1.1.8" 1437 | }, 1438 | "pytz": { 1439 | "hashes": [ 1440 | "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91", 1441 | "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174" 1442 | ], 1443 | "version": "==2022.4" 1444 | }, 1445 | "pytzdata": { 1446 | "hashes": [ 1447 | "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540", 1448 | "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f" 1449 | ], 1450 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1451 | "version": "==2020.1" 1452 | }, 1453 | "pyyaml": { 1454 | "hashes": [ 1455 | "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", 1456 | "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", 1457 | "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", 1458 | "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", 1459 | "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", 1460 | "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", 1461 | "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", 1462 | "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", 1463 | "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", 1464 | "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", 1465 | "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", 1466 | "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", 1467 | "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", 1468 | "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", 1469 | "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", 1470 | "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", 1471 | "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", 1472 | "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", 1473 | "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", 1474 | "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", 1475 | "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", 1476 | "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", 1477 | "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", 1478 | "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", 1479 | "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", 1480 | "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", 1481 | "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", 1482 | "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", 1483 | "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", 1484 | "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", 1485 | "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", 1486 | "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", 1487 | "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", 1488 | "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", 1489 | "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", 1490 | "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", 1491 | "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", 1492 | "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", 1493 | "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", 1494 | "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" 1495 | ], 1496 | "index": "pypi", 1497 | "version": "==6.0" 1498 | }, 1499 | "requests": { 1500 | "hashes": [ 1501 | "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", 1502 | "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" 1503 | ], 1504 | "index": "pypi", 1505 | "version": "==2.28.1" 1506 | }, 1507 | "requests-toolbelt": { 1508 | "hashes": [ 1509 | "sha256:64c6b8c51b515d123f9f708a29743f44eb70c4479440641ed2df8c4dea56d985", 1510 | "sha256:f695d6207931200b46c8ef6addbc8a921fb5d77cc4cd209c2e7d39293fcd2b30" 1511 | ], 1512 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1513 | "version": "==0.10.0" 1514 | }, 1515 | "rfc3986": { 1516 | "extras": [ 1517 | "idna2008" 1518 | ], 1519 | "hashes": [ 1520 | "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835", 1521 | "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97" 1522 | ], 1523 | "version": "==1.5.0" 1524 | }, 1525 | "rich": { 1526 | "hashes": [ 1527 | "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e", 1528 | "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0" 1529 | ], 1530 | "markers": "python_full_version >= '3.6.3' and python_full_version < '4.0.0'", 1531 | "version": "==12.6.0" 1532 | }, 1533 | "s3transfer": { 1534 | "hashes": [ 1535 | "sha256:06176b74f3a15f61f1b4f25a1fc29a4429040b7647133a463da8fa5bd28d5ecd", 1536 | "sha256:2ed07d3866f523cc561bf4a00fc5535827981b117dd7876f036b0c1aca42c947" 1537 | ], 1538 | "markers": "python_version >= '3.7'", 1539 | "version": "==0.6.0" 1540 | }, 1541 | "setproctitle": { 1542 | "hashes": [ 1543 | "sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b", 1544 | "sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9", 1545 | "sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31", 1546 | "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83", 1547 | "sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f", 1548 | "sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca", 1549 | "sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494", 1550 | "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe", 1551 | "sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172", 1552 | "sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084", 1553 | "sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4", 1554 | "sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1", 1555 | "sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c", 1556 | "sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973", 1557 | "sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c", 1558 | "sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202", 1559 | "sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5", 1560 | "sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65", 1561 | "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18", 1562 | "sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13", 1563 | "sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005", 1564 | "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02", 1565 | "sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7", 1566 | "sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665", 1567 | "sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f", 1568 | "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1", 1569 | "sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989", 1570 | "sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827", 1571 | "sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42", 1572 | "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927", 1573 | "sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122", 1574 | "sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9", 1575 | "sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f", 1576 | "sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d", 1577 | "sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98", 1578 | "sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796", 1579 | "sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb", 1580 | "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd", 1581 | "sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe", 1582 | "sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806", 1583 | "sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484", 1584 | "sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e", 1585 | "sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575", 1586 | "sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76", 1587 | "sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246", 1588 | "sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb", 1589 | "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099", 1590 | "sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c", 1591 | "sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258", 1592 | "sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865", 1593 | "sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b", 1594 | "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d", 1595 | "sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10", 1596 | "sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f", 1597 | "sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94", 1598 | "sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6", 1599 | "sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963", 1600 | "sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4", 1601 | "sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8", 1602 | "sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761" 1603 | ], 1604 | "markers": "python_version >= '3.7'", 1605 | "version": "==1.3.2" 1606 | }, 1607 | "setuptools": { 1608 | "hashes": [ 1609 | "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012", 1610 | "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e" 1611 | ], 1612 | "markers": "python_version >= '3.7'", 1613 | "version": "==65.4.1" 1614 | }, 1615 | "six": { 1616 | "hashes": [ 1617 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 1618 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 1619 | ], 1620 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1621 | "version": "==1.16.0" 1622 | }, 1623 | "sniffio": { 1624 | "hashes": [ 1625 | "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101", 1626 | "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384" 1627 | ], 1628 | "markers": "python_version >= '3.7'", 1629 | "version": "==1.3.0" 1630 | }, 1631 | "soupsieve": { 1632 | "hashes": [ 1633 | "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759", 1634 | "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d" 1635 | ], 1636 | "markers": "python_version >= '3.6'", 1637 | "version": "==2.3.2.post1" 1638 | }, 1639 | "sqlalchemy": { 1640 | "hashes": [ 1641 | "sha256:065ac7331b87494a86bf3dc4430c1ee7779d6dc532213c528394ddd00804e518", 1642 | "sha256:099e63ffad329989080c533896267c40f9cb38ed5704168f7dae3afdda121e10", 1643 | "sha256:0d8aab144cf8d31c1ac834802c7df4430248f74bd8b3ed3149f9c9eec0eafe50", 1644 | "sha256:230b210fc6d1af5d555d1d04ff9bd4259d6ab82b020369724ab4a1c805a32dd3", 1645 | "sha256:25aaf0bec9eadde9789e3c0178c718ae6923b57485fdeae85999bc3089d9b871", 1646 | "sha256:29816a338982c30dd7ee76c4e79f17d5991abb1b6561e9f1d72703d030a79c86", 1647 | "sha256:2e1b8d31c97a2b91aea8ed8299ad360a32d60728a89f2aac9c98eef07a633a0e", 1648 | "sha256:343c679899afdc4952ac659dc46f2075a2bd4fba87ca0df264be838eecd02096", 1649 | "sha256:386f215248c3fb2fab9bb77f631bc3c6cd38354ca2363d241784f8297d16b80a", 1650 | "sha256:457a1652bc1c5f832165ff341380b3742bfb98b9ceca24576350992713ad700f", 1651 | "sha256:4e554872766d2783abf0a11704536596e8794229fb0fa63d311a74caae58c6c5", 1652 | "sha256:4edff2b4101a1c442fb1b17d594a5fdf99145f27c5eaffae12c26aef2bb2bf65", 1653 | "sha256:690fbca2a208314504a2ab46d3e7dae320247fcb1967863b9782a70bf49fc600", 1654 | "sha256:6c6090d73820dcf04549f0b6e80f67b46c8191f0e40bf09c6d6f8ece2464e8b6", 1655 | "sha256:7bdb0f972bc35054c05088e91cec8fa810c3aa565b690bae75c005ee430e12e8", 1656 | "sha256:815a8cdf9c0fa504d0bfbe83fb3e596b7663fc828b73259a20299c01330467aa", 1657 | "sha256:a28c7b96bc5beef585172ca9d79068ae7fa2527feaa26bd63371851d7894c66f", 1658 | "sha256:a8763fe4de02f746666161b130cc3e5d1494a6f5475f5622f05251739fc22e55", 1659 | "sha256:b0266e133d819d33b555798822606e876187a96798e2d8c9b7f85e419d73ef94", 1660 | "sha256:bb97aeaa699c43da62e35856ab56e5154d062c09a3593a2c12c67d6a21059920", 1661 | "sha256:bce6eaf7b9a3a445911e225570b8fd26b7e98654ac9f308a8a52addb64a2a488", 1662 | "sha256:c4485040d86d4b3d9aa509fd3c492de3687d9bf52fb85d66b33912ad068a088c", 1663 | "sha256:c6f228b79fd757d9ca539c9958190b3a44308f743dc7d83575aa0891033f6c86", 1664 | "sha256:cde2cf3ee76e8c538f2f43f5cf9252ad53404fc350801191128bab68f335a8b2", 1665 | "sha256:cfa4a336de7d32ae30b54f7b8ec888fb5c6313a1b7419a9d7b3f49cdd83012a3", 1666 | "sha256:cfbf2cf8e8ef0a1d23bfd0fa387057e6e522d55e43821f1d115941d913ee7762", 1667 | "sha256:e26791ac43806dec1f18d328596db87f1b37f9d8271997dd1233054b4c377f51", 1668 | "sha256:e7d262415e4adf148441bd9f10ae4e5498d6649962fabc62a64ec7b4891d56c5", 1669 | "sha256:e9e95568eafae18ac40d00694b82dc3febe653f81eee83204ef248563f39696d", 1670 | "sha256:ec7c33e22beac16b4c5348c41cd94cfee056152e55a0efc62843deebfc53fcb4", 1671 | "sha256:f239778cf03cd46da4962636501f6dea55af9b4684cd7ceee104ad4f0290e878", 1672 | "sha256:f31757972677fbe9132932a69a4f23db59187a072cc26427f56a3082b46b6dac", 1673 | "sha256:fbdcf9019e92253fc6aa0bcd5937302664c3a4d53884c425c0caa994e56c4421", 1674 | "sha256:fc82688695eacf77befc3d839df2bc7ff314cd1d547f120835acdcbac1a480b8" 1675 | ], 1676 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", 1677 | "version": "==1.4.9" 1678 | }, 1679 | "sqlalchemy-jsonfield": { 1680 | "hashes": [ 1681 | "sha256:766d0b25bdebf53f67ccfaf9975987f921965987b37bae3a95ba6e7855afe98b", 1682 | "sha256:db129c0e79f6b3c61ca88b340363e2cc2023a187a40b9992dfee33bc75c3a02c" 1683 | ], 1684 | "markers": "python_full_version >= '3.5.0'", 1685 | "version": "==1.0.0" 1686 | }, 1687 | "sqlalchemy-utils": { 1688 | "hashes": [ 1689 | "sha256:5c13b5d08adfaa85f3d4e8ec09a75136216fad41346980d02974a70a77988bf9", 1690 | "sha256:9f9afba607a40455cf703adfa9846584bf26168a0c5a60a70063b70d65051f4d" 1691 | ], 1692 | "markers": "python_version ~= '3.6'", 1693 | "version": "==0.38.3" 1694 | }, 1695 | "sqlparse": { 1696 | "hashes": [ 1697 | "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34", 1698 | "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268" 1699 | ], 1700 | "markers": "python_version >= '3.5'", 1701 | "version": "==0.4.3" 1702 | }, 1703 | "swagger-ui-bundle": { 1704 | "hashes": [ 1705 | "sha256:b462aa1460261796ab78fd4663961a7f6f347ce01760f1303bbbdf630f11f516", 1706 | "sha256:cea116ed81147c345001027325c1ddc9ca78c1ee7319935c3c75d3669279d575" 1707 | ], 1708 | "version": "==0.0.9" 1709 | }, 1710 | "tabulate": { 1711 | "hashes": [ 1712 | "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", 1713 | "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f" 1714 | ], 1715 | "markers": "python_version >= '3.7'", 1716 | "version": "==0.9.0" 1717 | }, 1718 | "tenacity": { 1719 | "hashes": [ 1720 | "sha256:35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac", 1721 | "sha256:e48c437fdf9340f5666b92cd7990e96bc5fc955e1298baf4a907e3972067a445" 1722 | ], 1723 | "markers": "python_version >= '3.6'", 1724 | "version": "==8.1.0" 1725 | }, 1726 | "termcolor": { 1727 | "hashes": [ 1728 | "sha256:6b2cf769e93364a2676e1de56a7c0cff2cf5bd07f37e9cc80b0dd6320ebfe388", 1729 | "sha256:7e597f9de8e001a3208c4132938597413b9da45382b6f1d150cff8d062b7aaa3" 1730 | ], 1731 | "markers": "python_version >= '3.7'", 1732 | "version": "==2.0.1" 1733 | }, 1734 | "text-unidecode": { 1735 | "hashes": [ 1736 | "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", 1737 | "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" 1738 | ], 1739 | "version": "==1.3" 1740 | }, 1741 | "tomli": { 1742 | "hashes": [ 1743 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 1744 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 1745 | ], 1746 | "markers": "python_version < '3.11'", 1747 | "version": "==2.0.1" 1748 | }, 1749 | "typing-extensions": { 1750 | "hashes": [ 1751 | "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", 1752 | "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" 1753 | ], 1754 | "markers": "python_version >= '3.7'", 1755 | "version": "==4.4.0" 1756 | }, 1757 | "unicodecsv": { 1758 | "hashes": [ 1759 | "sha256:018c08037d48649a0412063ff4eda26eaa81eff1546dbffa51fa5293276ff7fc" 1760 | ], 1761 | "version": "==0.14.1" 1762 | }, 1763 | "urllib3": { 1764 | "hashes": [ 1765 | "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", 1766 | "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" 1767 | ], 1768 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'", 1769 | "version": "==1.26.12" 1770 | }, 1771 | "werkzeug": { 1772 | "hashes": [ 1773 | "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", 1774 | "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" 1775 | ], 1776 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1777 | "version": "==1.0.1" 1778 | }, 1779 | "wrapt": { 1780 | "hashes": [ 1781 | "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", 1782 | "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", 1783 | "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", 1784 | "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", 1785 | "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", 1786 | "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", 1787 | "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", 1788 | "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", 1789 | "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", 1790 | "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", 1791 | "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", 1792 | "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", 1793 | "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", 1794 | "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", 1795 | "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", 1796 | "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", 1797 | "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", 1798 | "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", 1799 | "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", 1800 | "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", 1801 | "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", 1802 | "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", 1803 | "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", 1804 | "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", 1805 | "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", 1806 | "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", 1807 | "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", 1808 | "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", 1809 | "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", 1810 | "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", 1811 | "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", 1812 | "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", 1813 | "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", 1814 | "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", 1815 | "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", 1816 | "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", 1817 | "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", 1818 | "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", 1819 | "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", 1820 | "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", 1821 | "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", 1822 | "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", 1823 | "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", 1824 | "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", 1825 | "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", 1826 | "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", 1827 | "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", 1828 | "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", 1829 | "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", 1830 | "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", 1831 | "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", 1832 | "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", 1833 | "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", 1834 | "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", 1835 | "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", 1836 | "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", 1837 | "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", 1838 | "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", 1839 | "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", 1840 | "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", 1841 | "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", 1842 | "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", 1843 | "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", 1844 | "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" 1845 | ], 1846 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1847 | "version": "==1.14.1" 1848 | }, 1849 | "wtforms": { 1850 | "hashes": [ 1851 | "sha256:7b504fc724d0d1d4d5d5c114e778ec88c37ea53144683e084215eed5155ada4c", 1852 | "sha256:81195de0ac94fbc8368abbaf9197b88c4f3ffd6c2719b5bf5fc9da744f3d829c" 1853 | ], 1854 | "version": "==2.3.3" 1855 | }, 1856 | "zipp": { 1857 | "hashes": [ 1858 | "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", 1859 | "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" 1860 | ], 1861 | "markers": "python_version >= '3.7'", 1862 | "version": "==3.8.1" 1863 | } 1864 | }, 1865 | "develop": { 1866 | "appdirs": { 1867 | "hashes": [ 1868 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 1869 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 1870 | ], 1871 | "version": "==1.4.4" 1872 | }, 1873 | "attrs": { 1874 | "hashes": [ 1875 | "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6", 1876 | "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700" 1877 | ], 1878 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 1879 | "version": "==20.3.0" 1880 | }, 1881 | "black": { 1882 | "hashes": [ 1883 | "sha256:14ff67aec0a47c424bc99b71005202045dc09270da44a27848d534600ac64fc7", 1884 | "sha256:197df8509263b0b8614e1df1756b1dd41be6738eed2ba9e9769f3880c2b9d7b6", 1885 | "sha256:1e464456d24e23d11fced2bc8c47ef66d471f845c7b7a42f3bd77bf3d1789650", 1886 | "sha256:2039230db3c6c639bd84efe3292ec7b06e9214a2992cd9beb293d639c6402edb", 1887 | "sha256:21199526696b8f09c3997e2b4db8d0b108d801a348414264d2eb8eb2532e540d", 1888 | "sha256:2644b5d63633702bc2c5f3754b1b475378fbbfb481f62319388235d0cd104c2d", 1889 | "sha256:432247333090c8c5366e69627ccb363bc58514ae3e63f7fc75c54b1ea80fa7de", 1890 | "sha256:444ebfb4e441254e87bad00c661fe32df9969b2bf224373a448d8aca2132b395", 1891 | "sha256:5b9b29da4f564ba8787c119f37d174f2b69cdfdf9015b7d8c5c16121ddc054ae", 1892 | "sha256:5cc42ca67989e9c3cf859e84c2bf014f6633db63d1cbdf8fdb666dcd9e77e3fa", 1893 | "sha256:5d8f74030e67087b219b032aa33a919fae8806d49c867846bfacde57f43972ef", 1894 | "sha256:72ef3925f30e12a184889aac03d77d031056860ccae8a1e519f6cbb742736383", 1895 | "sha256:819dc789f4498ecc91438a7de64427c73b45035e2e3680c92e18795a839ebb66", 1896 | "sha256:915ace4ff03fdfff953962fa672d44be269deb2eaf88499a0f8805221bc68c87", 1897 | "sha256:9311e99228ae10023300ecac05be5a296f60d2fd10fff31cf5c1fa4ca4b1988d", 1898 | "sha256:974308c58d057a651d182208a484ce80a26dac0caef2895836a92dd6ebd725e0", 1899 | "sha256:b8b49776299fece66bffaafe357d929ca9451450f5466e997a7285ab0fe28e3b", 1900 | "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458", 1901 | "sha256:e41a86c6c650bcecc6633ee3180d80a025db041a8e2398dcc059b3afa8382cd4", 1902 | "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1", 1903 | "sha256:fba8a281e570adafb79f7755ac8721b6cf1bbf691186a287e990c7929c7692ff" 1904 | ], 1905 | "index": "pypi", 1906 | "version": "==22.10.0" 1907 | }, 1908 | "chardet": { 1909 | "hashes": [ 1910 | "sha256:0368df2bfd78b5fc20572bb4e9bb7fb53e2c094f60ae9993339e8671d0afb8aa", 1911 | "sha256:d3e64f022d254183001eccc5db4040520c0f23b1a3f33d6413e099eb7f126557" 1912 | ], 1913 | "markers": "python_version >= '3.6'", 1914 | "version": "==5.0.0" 1915 | }, 1916 | "click": { 1917 | "hashes": [ 1918 | "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", 1919 | "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" 1920 | ], 1921 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1922 | "version": "==7.1.2" 1923 | }, 1924 | "colorama": { 1925 | "hashes": [ 1926 | "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da", 1927 | "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4" 1928 | ], 1929 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1930 | "version": "==0.4.5" 1931 | }, 1932 | "diff-cover": { 1933 | "hashes": [ 1934 | "sha256:16f2fe42885fee23400b886675d813ce72b9ef382856049fc1cd1a3f5b07357a", 1935 | "sha256:69258355daf8276057a92e42cec52594ad8cfe7dd505d6f0e56fe440bc4418d0" 1936 | ], 1937 | "markers": "python_full_version >= '3.7.2' and python_full_version < '4.0.0'", 1938 | "version": "==7.0.1" 1939 | }, 1940 | "iniconfig": { 1941 | "hashes": [ 1942 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 1943 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 1944 | ], 1945 | "version": "==1.1.1" 1946 | }, 1947 | "jinja2": { 1948 | "hashes": [ 1949 | "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", 1950 | "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" 1951 | ], 1952 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 1953 | "version": "==2.11.3" 1954 | }, 1955 | "markupsafe": { 1956 | "hashes": [ 1957 | "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", 1958 | "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", 1959 | "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", 1960 | "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194", 1961 | "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", 1962 | "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", 1963 | "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724", 1964 | "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", 1965 | "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646", 1966 | "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", 1967 | "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6", 1968 | "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a", 1969 | "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6", 1970 | "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad", 1971 | "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", 1972 | "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38", 1973 | "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac", 1974 | "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", 1975 | "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6", 1976 | "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047", 1977 | "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", 1978 | "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", 1979 | "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b", 1980 | "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", 1981 | "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", 1982 | "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a", 1983 | "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", 1984 | "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1", 1985 | "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9", 1986 | "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864", 1987 | "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", 1988 | "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee", 1989 | "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f", 1990 | "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", 1991 | "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", 1992 | "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", 1993 | "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", 1994 | "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b", 1995 | "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", 1996 | "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86", 1997 | "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6", 1998 | "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", 1999 | "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", 2000 | "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", 2001 | "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28", 2002 | "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e", 2003 | "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", 2004 | "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", 2005 | "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f", 2006 | "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d", 2007 | "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", 2008 | "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", 2009 | "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145", 2010 | "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", 2011 | "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c", 2012 | "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1", 2013 | "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a", 2014 | "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207", 2015 | "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", 2016 | "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53", 2017 | "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd", 2018 | "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134", 2019 | "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85", 2020 | "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9", 2021 | "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", 2022 | "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", 2023 | "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", 2024 | "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", 2025 | "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" 2026 | ], 2027 | "markers": "python_version >= '3.6'", 2028 | "version": "==2.0.1" 2029 | }, 2030 | "mypy-extensions": { 2031 | "hashes": [ 2032 | "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", 2033 | "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" 2034 | ], 2035 | "version": "==0.4.3" 2036 | }, 2037 | "packaging": { 2038 | "hashes": [ 2039 | "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", 2040 | "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" 2041 | ], 2042 | "markers": "python_version >= '3.6'", 2043 | "version": "==21.3" 2044 | }, 2045 | "pathspec": { 2046 | "hashes": [ 2047 | "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", 2048 | "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1" 2049 | ], 2050 | "version": "==0.9.0" 2051 | }, 2052 | "platformdirs": { 2053 | "hashes": [ 2054 | "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", 2055 | "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" 2056 | ], 2057 | "markers": "python_version >= '3.7'", 2058 | "version": "==2.5.2" 2059 | }, 2060 | "pluggy": { 2061 | "hashes": [ 2062 | "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", 2063 | "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" 2064 | ], 2065 | "markers": "python_version >= '3.6'", 2066 | "version": "==1.0.0" 2067 | }, 2068 | "py": { 2069 | "hashes": [ 2070 | "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", 2071 | "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" 2072 | ], 2073 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 2074 | "version": "==1.11.0" 2075 | }, 2076 | "pygments": { 2077 | "hashes": [ 2078 | "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", 2079 | "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" 2080 | ], 2081 | "markers": "python_version >= '3.6'", 2082 | "version": "==2.13.0" 2083 | }, 2084 | "pyparsing": { 2085 | "hashes": [ 2086 | "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", 2087 | "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" 2088 | ], 2089 | "markers": "python_full_version >= '3.6.8'", 2090 | "version": "==3.0.9" 2091 | }, 2092 | "pytest": { 2093 | "hashes": [ 2094 | "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", 2095 | "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" 2096 | ], 2097 | "index": "pypi", 2098 | "version": "==7.1.3" 2099 | }, 2100 | "pyyaml": { 2101 | "hashes": [ 2102 | "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", 2103 | "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", 2104 | "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", 2105 | "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", 2106 | "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", 2107 | "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", 2108 | "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", 2109 | "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", 2110 | "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", 2111 | "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", 2112 | "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", 2113 | "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", 2114 | "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", 2115 | "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", 2116 | "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", 2117 | "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", 2118 | "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", 2119 | "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", 2120 | "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", 2121 | "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", 2122 | "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", 2123 | "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", 2124 | "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", 2125 | "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", 2126 | "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", 2127 | "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", 2128 | "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", 2129 | "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", 2130 | "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", 2131 | "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", 2132 | "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", 2133 | "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", 2134 | "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", 2135 | "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", 2136 | "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", 2137 | "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", 2138 | "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", 2139 | "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", 2140 | "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", 2141 | "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" 2142 | ], 2143 | "index": "pypi", 2144 | "version": "==6.0" 2145 | }, 2146 | "regex": { 2147 | "hashes": [ 2148 | "sha256:003a2e1449d425afc817b5f0b3d4c4aa9072dd5f3dfbf6c7631b8dc7b13233de", 2149 | "sha256:0385d66e73cdd4462f3cc42c76a6576ddcc12472c30e02a2ae82061bff132c32", 2150 | "sha256:0394265391a86e2bbaa7606e59ac71bd9f1edf8665a59e42771a9c9adbf6fd4f", 2151 | "sha256:03ff695518482b946a6d3d4ce9cbbd99a21320e20d94913080aa3841f880abcd", 2152 | "sha256:079c182f99c89524069b9cd96f5410d6af437e9dca576a7d59599a574972707e", 2153 | "sha256:091efcfdd4178a7e19a23776dc2b1fafb4f57f4d94daf340f98335817056f874", 2154 | "sha256:0b664a4d33ffc6be10996606dfc25fd3248c24cc589c0b139feb4c158053565e", 2155 | "sha256:14216ea15efc13f28d0ef1c463d86d93ca7158a79cd4aec0f9273f6d4c6bb047", 2156 | "sha256:14a7ab070fa3aec288076eed6ed828587b805ef83d37c9bfccc1a4a7cfbd8111", 2157 | "sha256:14c71437ffb89479c89cc7022a5ea2075a842b728f37205e47c824cc17b30a42", 2158 | "sha256:18e503b1e515a10282b3f14f1b3d856194ecece4250e850fad230842ed31227f", 2159 | "sha256:19a4da6f513045f5ba00e491215bd00122e5bd131847586522463e5a6b2bd65f", 2160 | "sha256:1a901ce5cd42658ab8f8eade51b71a6d26ad4b68c7cfc86b87efc577dfa95602", 2161 | "sha256:26df88c9636a0c3f3bd9189dd435850a0c49d0b7d6e932500db3f99a6dd604d1", 2162 | "sha256:2dda4b096a6f630d6531728a45bd12c67ec3badf44342046dc77d4897277d4f2", 2163 | "sha256:322bd5572bed36a5b39952d88e072738926759422498a96df138d93384934ff8", 2164 | "sha256:360ffbc9357794ae41336b681dff1c0463193199dfb91fcad3ec385ea4972f46", 2165 | "sha256:37e5a26e76c46f54b3baf56a6fdd56df9db89758694516413757b7d127d4c57b", 2166 | "sha256:3d64e1a7e6d98a4cdc8b29cb8d8ed38f73f49e55fbaa737bdb5933db99b9de22", 2167 | "sha256:3f3b4594d564ed0b2f54463a9f328cf6a5b2a32610a90cdff778d6e3e561d08b", 2168 | "sha256:4146cb7ae6029fc83b5c905ec6d806b7e5568dc14297c423e66b86294bad6c39", 2169 | "sha256:4318f69b79f9f7d84a7420e97d4bfe872dc767c72f891d4fea5fa721c74685f7", 2170 | "sha256:4cdbfa6d2befeaee0c899f19222e9b20fc5abbafe5e9c43a46ef819aeb7b75e5", 2171 | "sha256:50e764ffbd08b06aa8c4e86b8b568b6722c75d301b33b259099f237c46b2134e", 2172 | "sha256:518272f25da93e02af4f1e94985f5042cec21557ef3591027d0716f2adda5d0a", 2173 | "sha256:592b9e2e1862168e71d9e612bfdc22c451261967dbd46681f14e76dfba7105fd", 2174 | "sha256:59a786a55d00439d8fae4caaf71581f2aaef7297d04ee60345c3594efef5648a", 2175 | "sha256:59bac44b5a07b08a261537f652c26993af9b1bbe2a29624473968dd42fc29d56", 2176 | "sha256:5d0dd8b06896423211ce18fba0c75dacc49182a1d6514c004b535be7163dca0f", 2177 | "sha256:67a4c625361db04ae40ef7c49d3cbe2c1f5ff10b5a4491327ab20f19f2fb5d40", 2178 | "sha256:6adfe300848d61a470ec7547adc97b0ccf86de86a99e6830f1d8c8d19ecaf6b3", 2179 | "sha256:6b32b45433df1fad7fed738fe15200b6516da888e0bd1fdd6aa5e50cc16b76bc", 2180 | "sha256:6c57d50d4d5eb0c862569ca3c840eba2a73412f31d9ecc46ef0d6b2e621a592b", 2181 | "sha256:6d43bd402b27e0e7eae85c612725ba1ce7798f20f6fab4e8bc3de4f263294f03", 2182 | "sha256:6e521d9db006c5e4a0f8acfef738399f72b704913d4e083516774eb51645ad7c", 2183 | "sha256:6fe1dd1021e0f8f3f454ce2811f1b0b148f2d25bb38c712fec00316551e93650", 2184 | "sha256:73b985c9fc09a7896846e26d7b6f4d1fd5a20437055f4ef985d44729f9f928d0", 2185 | "sha256:7681c49da1a2d4b905b4f53d86c9ba4506e79fba50c4a664d9516056e0f7dfcc", 2186 | "sha256:77c2879d3ba51e5ca6c2b47f2dcf3d04a976a623a8fc8236010a16c9e0b0a3c7", 2187 | "sha256:7b0c5cc3d1744a67c3b433dce91e5ef7c527d612354c1f1e8576d9e86bc5c5e2", 2188 | "sha256:7fcf7f94ccad19186820ac67e2ec7e09e0ac2dac39689f11cf71eac580503296", 2189 | "sha256:83cc32a1a2fa5bac00f4abc0e6ce142e3c05d3a6d57e23bd0f187c59b4e1e43b", 2190 | "sha256:8418ee2cb857b83881b8f981e4c636bc50a0587b12d98cb9b947408a3c484fe7", 2191 | "sha256:86df2049b18745f3cd4b0f4c4ef672bfac4b80ca488e6ecfd2bbfe68d2423a2c", 2192 | "sha256:880dbeb6bdde7d926b4d8e41410b16ffcd4cb3b4c6d926280fea46e2615c7a01", 2193 | "sha256:8aba0d01e3dfd335f2cb107079b07fdddb4cd7fb2d8c8a1986f9cb8ce9246c24", 2194 | "sha256:8dcbcc9e72a791f622a32d17ff5011326a18996647509cac0609a7fc43adc229", 2195 | "sha256:944567bb08f52268d8600ee5bdf1798b2b62ea002cc692a39cec113244cbdd0d", 2196 | "sha256:995e70bb8c91d1b99ed2aaf8ec44863e06ad1dfbb45d7df95f76ef583ec323a9", 2197 | "sha256:99945ddb4f379bb9831c05e9f80f02f079ba361a0fb1fba1fc3b267639b6bb2e", 2198 | "sha256:9a165a05979e212b2c2d56a9f40b69c811c98a788964e669eb322de0a3e420b4", 2199 | "sha256:9bc8edc5f8ef0ebb46f3fa0d02bd825bbe9cc63d59e428ffb6981ff9672f6de1", 2200 | "sha256:a1aec4ae549fd7b3f52ceaf67e133010e2fba1538bf4d5fc5cd162a5e058d5df", 2201 | "sha256:a1c4d17879dd4c4432c08a1ca1ab379f12ab54af569e945b6fc1c4cf6a74ca45", 2202 | "sha256:a2b39ee3b280e15824298b97cec3f7cbbe6539d8282cc8a6047a455b9a72c598", 2203 | "sha256:a2effeaf50a6838f3dd4d3c5d265f06eabc748f476e8441892645ae3a697e273", 2204 | "sha256:a59d0377e58d96a6f11636e97992f5b51b7e1e89eb66332d1c01b35adbabfe8a", 2205 | "sha256:a926339356fe29595f8e37af71db37cd87ff764e15da8ad5129bbaff35bcc5a6", 2206 | "sha256:a9eb9558e1d0f78e07082d8a70d5c4d631c8dd75575fae92105df9e19c736730", 2207 | "sha256:ab07934725e6f25c6f87465976cc69aef1141e86987af49d8c839c3ffd367c72", 2208 | "sha256:ad75173349ad79f9d21e0d0896b27dcb37bfd233b09047bc0b4d226699cf5c87", 2209 | "sha256:b7b701dbc124558fd2b1b08005eeca6c9160e209108fbcbd00091fcfac641ac7", 2210 | "sha256:b7bee775ff05c9d519195bd9e8aaaccfe3971db60f89f89751ee0f234e8aeac5", 2211 | "sha256:b86548b8234b2be3985dbc0b385e35f5038f0f3e6251464b827b83ebf4ed90e5", 2212 | "sha256:b9d68eb704b24bc4d441b24e4a12653acd07d2c39940548761e0985a08bc1fff", 2213 | "sha256:c0b7cb9598795b01f9a3dd3f770ab540889259def28a3bf9b2fa24d52edecba3", 2214 | "sha256:cab548d6d972e1de584161487b2ac1aa82edd8430d1bde69587ba61698ad1cfb", 2215 | "sha256:ce331b076b2b013e7d7f07157f957974ef0b0881a808e8a4a4b3b5105aee5d04", 2216 | "sha256:cfa4c956ff0a977c4823cb3b930b0a4e82543b060733628fec7ab3eb9b1abe37", 2217 | "sha256:d23ac6b4bf9e32fcde5fcdb2e1fd5e7370d6693fcac51ee1d340f0e886f50d1f", 2218 | "sha256:d2885ec6eea629c648ecc9bde0837ec6b92208b7f36381689937fe5d64a517e8", 2219 | "sha256:d2a1371dc73e921f3c2e087c05359050f3525a9a34b476ebc8130e71bec55e97", 2220 | "sha256:d3102ab9bf16bf541ca228012d45d88d2a567c9682a805ae2c145a79d3141fdd", 2221 | "sha256:d5b003d248e6f292475cd24b04e5f72c48412231961a675edcb653c70730e79e", 2222 | "sha256:d5edd3eb877c9fc2e385173d4a4e1d792bf692d79e25c1ca391802d36ecfaa01", 2223 | "sha256:d7430f041755801b712ec804aaf3b094b9b5facbaa93a6339812a8e00d7bd53a", 2224 | "sha256:d837ccf3bd2474feabee96cd71144e991472e400ed26582edc8ca88ce259899c", 2225 | "sha256:dab81cc4d58026861445230cfba27f9825e9223557926e7ec22156a1a140d55c", 2226 | "sha256:db45016364eec9ddbb5af93c8740c5c92eb7f5fc8848d1ae04205a40a1a2efc6", 2227 | "sha256:df8fe00b60e4717662c7f80c810ba66dcc77309183c76b7754c0dff6f1d42054", 2228 | "sha256:e6e6e61e9a38b6cc60ca3e19caabc90261f070f23352e66307b3d21a24a34aaf", 2229 | "sha256:ee7045623a5ace70f3765e452528b4c1f2ce669ed31959c63f54de64fe2f6ff7", 2230 | "sha256:f06cc1190f3db3192ab8949e28f2c627e1809487e2cfc435b6524c1ce6a2f391", 2231 | "sha256:f07373b6e56a6f3a0df3d75b651a278ca7bd357a796078a26a958ea1ce0588fd", 2232 | "sha256:f6e0321921d2fdc082ef90c1fd0870f129c2e691bfdc4937dcb5cd308aba95c4", 2233 | "sha256:f6e167d1ccd41d27b7b6655bb7a2dcb1b1eb1e0d2d662043470bd3b4315d8b2b", 2234 | "sha256:fcbd1edff1473d90dc5cf4b52d355cf1f47b74eb7c85ba6e45f45d0116b8edbd", 2235 | "sha256:fe428822b7a8c486bcd90b334e9ab541ce6cc0d6106993d59f201853e5e14121" 2236 | ], 2237 | "markers": "python_version >= '3.6'", 2238 | "version": "==2022.9.13" 2239 | }, 2240 | "sqlfluff": { 2241 | "hashes": [ 2242 | "sha256:2a291d9c2ff742471c690c4bd1e760a483100fbcba23f330a61e7888f67c0c3f", 2243 | "sha256:538707af21d1e1f15a64512255b502eaf6d12d1b6edfd3b0b6cb979fcaec07b4" 2244 | ], 2245 | "index": "pypi", 2246 | "version": "==1.3.2" 2247 | }, 2248 | "tblib": { 2249 | "hashes": [ 2250 | "sha256:059bd77306ea7b419d4f76016aef6d7027cc8a0785579b5aad198803435f882c", 2251 | "sha256:289fa7359e580950e7d9743eab36b0691f0310fce64dee7d9c31065b8f723e23" 2252 | ], 2253 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", 2254 | "version": "==1.7.0" 2255 | }, 2256 | "toml": { 2257 | "hashes": [ 2258 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 2259 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 2260 | ], 2261 | "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", 2262 | "version": "==0.10.2" 2263 | }, 2264 | "tomli": { 2265 | "hashes": [ 2266 | "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", 2267 | "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" 2268 | ], 2269 | "markers": "python_version < '3.11'", 2270 | "version": "==2.0.1" 2271 | }, 2272 | "tqdm": { 2273 | "hashes": [ 2274 | "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", 2275 | "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" 2276 | ], 2277 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", 2278 | "version": "==4.64.1" 2279 | }, 2280 | "typing-extensions": { 2281 | "hashes": [ 2282 | "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa", 2283 | "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e" 2284 | ], 2285 | "markers": "python_version >= '3.7'", 2286 | "version": "==4.4.0" 2287 | } 2288 | } 2289 | } 2290 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Audiophile End-To-End ELT Pipeline 2 | 3 | Pipeline that extracts data from [Crinacle's](https://crinacle.com/) Headphone and InEarMonitor databases and finalizes data for a Metabase Dashboard. 4 | 5 | ## Architecture 6 | 7 | ![Architecture](https://github.com/ris-tlp/audiophile-e2e-pipeline/blob/main/images/architecture.jpeg) 8 | 9 | Infrastructure provisioning through [Terraform](https://www.terraform.io/), containerized through [Docker](https://www.docker.com/) and orchestrated through [Airflow](https://airflow.apache.org/). Created dashboard through [Metabase](https://www.metabase.com/). 10 | 11 | DAG Tasks: 12 | 13 | 1. Scrape data from [Crinacle's](https://crinacle.com/) website to generate bronze data. 14 | 2. Load bronze data to [AWS S3](https://aws.amazon.com/s3/). 15 | 3. Initial data parsing and validation through [Pydantic](https://github.com/pydantic/pydantic) to generate silver data. 16 | 4. Load silver data to [AWS S3](https://aws.amazon.com/s3/). 17 | 5. Load silver data to [AWS Redshift](https://aws.amazon.com/redshift/). 18 | 6. Load silver data to [AWS RDS](https://aws.amazon.com/rds/) for future projects. 19 | 7. and 8. Transform and test data through [dbt](https://docs.getdbt.com/) in the warehouse. 20 | 21 | ## Dashboard 22 | 23 | ![Dashboard](https://github.com/ris-tlp/audiophile-e2e-pipeline/blob/main/images/metabase_dashboard.jpeg) 24 | 25 | ## Requirements 26 | 27 | 1. Configure AWS account through [AWS CLI](https://aws.amazon.com/cli/). [Reqruired for Terraform] 28 | 2. [Terraform](https://www.terraform.io/). [Required to provision AWS services] 29 | 3. [Docker / Docker-Compose](https://www.docker.com/). [Required to run Airflow DAG / pipeline] 30 | 31 | ## Run Pipeline 32 | 33 | 1. `make infra`: create AWS services. You will be asked to enter a password for your Redshift and RDS clusters. 34 | 2. `make config`: generate configuration with Terraform outputs and AWS credentials. 35 | 3. `make base-build`: build base airflow image with project requirements. 36 | 4. `make build`: build docker images for airflow. 37 | 5. `make up`: run the pipeline. 38 | -------------------------------------------------------------------------------- /airflow/dags/main_dag.py: -------------------------------------------------------------------------------- 1 | from airflow import DAG 2 | from airflow.utils.dates import days_ago 3 | from airflow.operators.bash_operator import BashOperator 4 | from airflow_dbt.operators.dbt_operator import DbtRunOperator, DbtTestOperator 5 | 6 | 7 | schedule_interval = "@daily" 8 | start_date = days_ago(1) 9 | default_args = {"owner": "airflow", "depends_on_past": False, "retries": 1} 10 | 11 | with DAG( 12 | dag_id="audiophile_e2e_pipeline", 13 | schedule_interval=schedule_interval, 14 | default_args=default_args, 15 | start_date=start_date, 16 | catchup=True, 17 | max_active_runs=1, 18 | ) as dag: 19 | 20 | scrape_audiophile_data = BashOperator( 21 | task_id="scrape_audiophile_data", 22 | bash_command="python /opt/airflow/tasks/scraper_extract/scraper.py", 23 | ) 24 | 25 | upload_bronze_csv_s3 = BashOperator( 26 | task_id="upload_bronze_csv_to_s3", 27 | bash_command="python /opt/airflow/tasks/upload_to_s3.py bronze", 28 | ) 29 | 30 | validate_sanitize_bronze_data = BashOperator( 31 | task_id="validate_sanitize_bronze_data", 32 | bash_command="python /opt/airflow/tasks/validate_sanitize_bronze.py", 33 | ) 34 | 35 | upload_silver_csv_s3 = BashOperator( 36 | task_id="upload_silver_csv_to_s3", 37 | bash_command="python /opt/airflow/tasks/upload_to_s3.py silver", 38 | ) 39 | 40 | redshift_load = BashOperator( 41 | task_id="load_data_into_redshift", 42 | bash_command="python /opt/airflow/tasks/redshift_load/upload_to_redshift.py", 43 | ) 44 | 45 | rds_load = BashOperator( 46 | task_id="load_data_into_rds", 47 | bash_command="python /opt/airflow/tasks/rds_load/upload_to_rds.py", 48 | ) 49 | 50 | generate_dbt_profile = BashOperator( 51 | task_id="generate_dbt_profile", 52 | bash_command="python /opt/airflow/tasks/dbt_transform/generate_dbt_profile.py" 53 | ) 54 | 55 | dbt_transform = DbtRunOperator( 56 | task_id="run_dbt_transformations", 57 | dir="/opt/airflow/tasks/dbt_transform/", 58 | profiles_dir="/opt/airflow/tasks/dbt_transform" 59 | ) 60 | 61 | dbt_test = DbtTestOperator( 62 | task_id="run_dbt_tests", 63 | dir="/opt/airflow/tasks/dbt_transform/", 64 | profiles_dir="/opt/airflow/tasks/dbt_transform" 65 | ) 66 | 67 | 68 | ( 69 | scrape_audiophile_data 70 | >> upload_bronze_csv_s3 71 | >> validate_sanitize_bronze_data 72 | >> upload_silver_csv_s3 73 | >> redshift_load 74 | >> rds_load 75 | >> generate_dbt_profile 76 | >> dbt_transform 77 | >> dbt_test 78 | ) 79 | -------------------------------------------------------------------------------- /airflow/tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris-tlp/audiophile-e2e-pipeline/5ab3d132c3b941fb0240a93a6277717a296c341a/airflow/tasks/__init__.py -------------------------------------------------------------------------------- /airflow/tasks/configuration.env.example: -------------------------------------------------------------------------------- 1 | aws_region = 2 | bucket_name = 3 | rds_database_name = 4 | rds_instance_endpoint = 5 | rds_password = 6 | rds_port = 7 | rds_username = 8 | redshift_database = 9 | redshift_host = 10 | redshift_password = 11 | redshift_port = 12 | redshift_user = 13 | aws_access_key_id = 14 | aws_secret_access_key = 15 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/dbt_project.yml: -------------------------------------------------------------------------------- 1 | # Name your project! Project names should contain only lowercase characters 2 | # and underscores. A good package name should reflect your organization's 3 | # name or the intended use of these models 4 | name: "dbt_transform" 5 | version: "1.0.0" 6 | config-version: 2 7 | 8 | # This setting configures which "profile" dbt uses for this project. 9 | profile: "dbt_transform" 10 | 11 | # These configurations specify where dbt should look for different types of files. 12 | # The `model-paths` config, for example, states that models in this project can be 13 | # found in the "models/" directory. You probably won't need to change these! 14 | model-paths: ["models"] 15 | analysis-paths: ["analyses"] 16 | test-paths: ["tests"] 17 | seed-paths: ["seeds"] 18 | macro-paths: ["macros"] 19 | snapshot-paths: ["snapshots"] 20 | 21 | target-path: "target" # directory which will store compiled SQL files 22 | clean-targets: # directories to be removed by `dbt clean` 23 | - "target" 24 | - "dbt_packages" 25 | 26 | # Configuring models 27 | # Full documentation: https://docs.getdbt.com/docs/configuring-models 28 | 29 | # In this example config, we tell dbt to build all models in the example/ directory 30 | # as tables. These settings can be overridden in the individual model files 31 | # using the `{{ config(...) }}` macro. 32 | models: 33 | dbt_task: 34 | staging: 35 | +materialized: view 36 | +materialized: table 37 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/generate_dbt_profile.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | import pathlib 3 | from dotenv import dotenv_values 4 | 5 | # Load config 6 | configuration_path = pathlib.Path(__file__).parent.parent.resolve() 7 | script_path = pathlib.Path(__file__).parent.resolve() 8 | config = dotenv_values(f"{configuration_path}/configuration.env") 9 | 10 | dbt_profile = dict( 11 | dbt_transform = dict( 12 | target = "dev", 13 | outputs = dict( 14 | dev = dict( 15 | dbname = config["redshift_database"], 16 | host = config["redshift_host"].split(":")[0], 17 | password = config["redshift_password"], 18 | port = int(config["redshift_port"]), 19 | schema = "public", 20 | threads = 1, 21 | type = "redshift", 22 | user = config["redshift_user"] 23 | ) 24 | ) 25 | ) 26 | ) 27 | 28 | with open(f"{script_path}/profiles.yml", "w") as outfile: 29 | yaml.dump(dbt_profile, outfile, default_flow_style=False) 30 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/avg_audio_signature_headphone.sql: -------------------------------------------------------------------------------- 1 | WITH mapped_ranks AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_maprankvalues") }} 6 | ), 7 | final AS ( 8 | SELECT 9 | headphone.audio_signature, 10 | AVG(mapped_ranks.rank_value) AS average_rating, 11 | COUNT(headphone.audio_signature) AS number_of_products 12 | FROM 13 | headphone, 14 | mapped_ranks 15 | WHERE 16 | headphone.rank_grade = mapped_ranks.rank_grade 17 | GROUP BY 18 | headphone.audio_signature 19 | HAVING 20 | COUNT(headphone.audio_signature) > 15 21 | ) 22 | SELECT 23 | * 24 | FROM 25 | final 26 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/avg_audio_signature_iem.sql: -------------------------------------------------------------------------------- 1 | WITH mapped_ranks AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_maprankvalues") }} 6 | ), 7 | final AS ( 8 | SELECT 9 | inearmonitor.audio_signature, 10 | AVG(mapped_ranks.rank_value) AS average_rating, 11 | COUNT(inearmonitor.audio_signature) AS number_of_products 12 | FROM 13 | inearmonitor, 14 | mapped_ranks 15 | WHERE 16 | inearmonitor.rank_grade = mapped_ranks.rank_grade 17 | GROUP BY 18 | inearmonitor.audio_signature 19 | HAVING 20 | COUNT(inearmonitor.audio_signature) > 35 21 | ) 22 | SELECT 23 | * 24 | FROM 25 | final 26 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/avg_driver_rating_headphone.sql: -------------------------------------------------------------------------------- 1 | WITH mapped_ranks AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_maprankvalues") }} 6 | ), 7 | final AS ( 8 | SELECT 9 | headphone.driver_type, 10 | AVG(mapped_ranks.rank_value) AS average_rating, 11 | COUNT(headphone.driver_type) AS number_of_products 12 | FROM 13 | headphone, 14 | mapped_ranks 15 | WHERE 16 | headphone.rank_grade = mapped_ranks.rank_grade 17 | GROUP BY 18 | headphone.driver_type 19 | ) 20 | SELECT 21 | * 22 | FROM 23 | final 24 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/highest_rank_company_headphone.sql: -------------------------------------------------------------------------------- 1 | WITH mapped_ranks AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_maprankvalues") }} 6 | ), 7 | company AS ( 8 | SELECT 9 | * 10 | FROM 11 | {{ ref("stg_companynames") }} 12 | ), 13 | final AS ( 14 | SELECT 15 | company.company_name, 16 | AVG(mapped_ranks.rank_value) AS average_rating, 17 | COUNT(company.company_name) AS number_of_products 18 | 19 | FROM 20 | headphone, 21 | mapped_ranks, 22 | company 23 | WHERE 24 | headphone.rank_grade = mapped_ranks.rank_grade 25 | AND company.company_name = {{ dbt.split_part( 26 | string_text = 'headphone.model', 27 | delimiter_text = "' '", 28 | part_number = 1 29 | ) }} 30 | GROUP BY 31 | company.company_name 32 | ) 33 | SELECT 34 | * 35 | FROM 36 | final 37 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/highest_rank_company_iem.sql: -------------------------------------------------------------------------------- 1 | WITH mapped_ranks AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_maprankvalues") }} 6 | ), 7 | company AS ( 8 | SELECT 9 | * 10 | FROM 11 | {{ ref("stg_companynames") }} 12 | ), 13 | final AS ( 14 | SELECT 15 | company.company_name, 16 | AVG(mapped_ranks.rank_value) AS average_rating, 17 | COUNT(company.company_name) AS number_of_products 18 | FROM 19 | inearmonitor, 20 | mapped_ranks, 21 | company 22 | WHERE 23 | inearmonitor.rank_grade = mapped_ranks.rank_grade 24 | AND company.company_name = {{ dbt.split_part( 25 | string_text = 'InEarMonitor.model', 26 | delimiter_text = "' '", 27 | part_number = 1 28 | ) }} 29 | GROUP BY 30 | company.company_name 31 | ) 32 | SELECT 33 | * 34 | FROM 35 | final 36 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/highest_value_company_headphone.sql: -------------------------------------------------------------------------------- 1 | WITH company AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_companynames") }} 6 | ), 7 | final AS ( 8 | SELECT 9 | company.company_name, 10 | MAX(headphone.value_rating) AS highest_value 11 | FROM 12 | company, 13 | headphone 14 | WHERE 15 | company.company_name = {{ dbt.split_part( 16 | string_text = 'headphone.model', 17 | delimiter_text = "' '", 18 | part_number = 1 19 | ) }} 20 | GROUP BY 21 | company.company_name 22 | ) 23 | SELECT 24 | * 25 | FROM 26 | final 27 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/highest_value_company_iem.sql: -------------------------------------------------------------------------------- 1 | WITH company AS ( 2 | SELECT 3 | * 4 | FROM 5 | {{ ref("stg_companynames") }} 6 | ), 7 | final AS ( 8 | SELECT 9 | company.company_name, 10 | MAX(inearmonitor.value_rating) AS highest_value 11 | FROM 12 | company, 13 | inearmonitor 14 | WHERE 15 | company.company_name = {{ dbt.split_part( 16 | string_text = 'InEarMonitor.model', 17 | delimiter_text = "' '", 18 | part_number = 1 19 | ) }} 20 | GROUP BY 21 | company.company_name 22 | ) 23 | SELECT 24 | * 25 | FROM 26 | final 27 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/schema.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | models: 4 | - name: highest_value_company_iem 5 | columns: 6 | - name: company_name 7 | tests: 8 | - unique 9 | - not_null 10 | - name: highest_value 11 | tests: 12 | - not_null 13 | 14 | - name: highest_value_company_headphone 15 | columns: 16 | - name: company_name 17 | tests: 18 | - unique 19 | - not_null 20 | - name: highest_value 21 | tests: 22 | - not_null 23 | 24 | - name: highest_rank_company_iem 25 | columns: 26 | - name: company_name 27 | tests: 28 | - unique 29 | - not_null 30 | - name: average_rating 31 | tests: 32 | - not_null 33 | - name: number_of_products 34 | tests: 35 | - not_null 36 | 37 | - name: highest_rank_company_headphone 38 | columns: 39 | - name: company_name 40 | tests: 41 | - unique 42 | - not_null 43 | - name: average_rating 44 | tests: 45 | - not_null 46 | - name: number_of_products 47 | tests: 48 | - not_null 49 | 50 | - name: avg_driver_rating_headphone 51 | columns: 52 | - name: driver_type 53 | tests: 54 | - unique 55 | - not_null 56 | - name: average_rating 57 | tests: 58 | - not_null 59 | - name: number_of_products 60 | tests: 61 | - not_null 62 | 63 | - name: avg_audio_signature_headphone 64 | columns: 65 | - name: audio_signature 66 | tests: 67 | - unique 68 | - not_null 69 | - name: average_rating 70 | tests: 71 | - not_null 72 | - name: number_of_products 73 | tests: 74 | - not_null 75 | 76 | - name: avg_audio_signature_iem 77 | columns: 78 | - name: audio_signature 79 | tests: 80 | - unique 81 | - not_null 82 | - name: average_rating 83 | tests: 84 | - not_null 85 | - name: number_of_products 86 | tests: 87 | - not_null 88 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/staging/schema.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | models: 4 | - name: stg_companynames 5 | columns: 6 | - name: company_name 7 | tests: 8 | - unique 9 | - not_null 10 | - name: stg_maprankvalues 11 | columns: 12 | - name: rank_grade 13 | tests: 14 | - unique 15 | - not_null 16 | - name: rank_value 17 | tests: 18 | - unique 19 | - not_null 20 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/staging/stg_companynames.sql: -------------------------------------------------------------------------------- 1 | WITH iem AS ( 2 | SELECT 3 | DISTINCT {{ dbt.split_part( 4 | string_text = 'model', 5 | delimiter_text = "' '", 6 | part_number = 1 7 | )}} AS company_name 8 | FROM 9 | public.InEarMonitor 10 | ), 11 | headphone AS ( 12 | SELECT 13 | DISTINCT {{ dbt.split_part( 14 | string_text = 'model', 15 | delimiter_text = "' '", 16 | part_number = 1 17 | )}} AS company_name 18 | FROM 19 | public.Headphone 20 | ), 21 | final AS ( 22 | SELECT 23 | iem.company_name 24 | FROM 25 | iem 26 | LEFT JOIN headphone ON iem.company_name = headphone.company_name 27 | ) 28 | SELECT 29 | * 30 | FROM 31 | final 32 | -------------------------------------------------------------------------------- /airflow/tasks/dbt_transform/models/staging/stg_maprankvalues.sql: -------------------------------------------------------------------------------- 1 | -- Each query iterates over the possible rank values for each audio device model and 2 | -- assigns a numerical value to each rank. 3 | 4 | {% set rank_grades = ["S+", "S", "S-", "A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "E+", "E", "E-", "F+", "F", "F-"] %} 5 | {% set ns = namespace(value=10) %} 6 | 7 | WITH iem_ranks AS ( 8 | SELECT DISTINCT 9 | rank_grade, 10 | CASE 11 | {% for grade in rank_grades %} 12 | WHEN rank_grade = '{{ grade }}' THEN {{ ns.value }} 13 | {% set ns.value = ns.value + 0.5 %} 14 | {% endfor %} 15 | END AS rank_value 16 | FROM 17 | inearmonitor 18 | ), 19 | 20 | {% set ns = namespace(value=10) %} 21 | 22 | headphone_ranks AS ( 23 | SELECT DISTINCT 24 | rank_grade, 25 | CASE 26 | {% for grade in rank_grades %} 27 | WHEN rank_grade = '{{ grade }}' THEN {{ ns.value }} 28 | {% set ns.value = ns.value + 0.5 %} 29 | {% endfor %} 30 | END AS rank_value 31 | FROM 32 | headphone 33 | ), 34 | 35 | final AS ( 36 | SELECT 37 | rank_grade, 38 | rank_value 39 | FROM iem_ranks 40 | UNION 41 | SELECT 42 | rank_grade, 43 | rank_value 44 | FROM headphone_ranks 45 | ) 46 | 47 | SELECT * FROM final 48 | -------------------------------------------------------------------------------- /airflow/tasks/rds_load/queries/create_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS Headphone ( 2 | rank_grade varchar(5) NOT NULL, 3 | value_rating int NOT NULL, 4 | model varchar(500) NOT NULL PRIMARY KEY, 5 | price int NOT NULL, 6 | audio_signature varchar(200) NOT NULL, 7 | tone_grade varchar(5) NOT NULL, 8 | technical_grade varchar(5) NOT NULL, 9 | driver_type varchar(200), 10 | fit_cup varchar(200) 11 | ); 12 | 13 | CREATE TABLE IF NOT EXISTS InEarMonitor ( 14 | rank_grade varchar(5) NOT NULL, 15 | value_rating int NOT NULL, 16 | model varchar(500) NOT NULL PRIMARY KEY, 17 | price int NOT NULL, 18 | audio_signature varchar(200) NOT NULL, 19 | tone_grade varchar(5) NOT NULL, 20 | technical_grade varchar(5) NOT NULL, 21 | driver_type varchar(200) 22 | ); 23 | -------------------------------------------------------------------------------- /airflow/tasks/rds_load/queries/load_main_tables.sql: -------------------------------------------------------------------------------- 1 | BEGIN TRANSACTION; 2 | 3 | -- Delete rows through inner join 4 | DELETE FROM 5 | InEarMonitor USING InEarMonitor_temp 6 | WHERE 7 | InEarMonitor.model = InEarMonitor_temp.model; 8 | 9 | DELETE FROM 10 | Headphone USING Headphone_temp 11 | WHERE 12 | Headphone.model = Headphone_temp.model; 13 | 14 | -- Move data from temp table to main tables 15 | INSERT INTO 16 | InEarMonitor 17 | SELECT 18 | * 19 | FROM 20 | InEarMonitor_temp; 21 | 22 | INSERT INTO 23 | Headphone 24 | SELECT 25 | * 26 | FROM 27 | Headphone_temp; 28 | 29 | END TRANSACTION; 30 | 31 | -- Drop temp tables 32 | DROP TABLE InEarMonitor_temp; 33 | 34 | DROP TABLE Headphone_temp; 35 | -------------------------------------------------------------------------------- /airflow/tasks/rds_load/queries/load_temp_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION IF NOT EXISTS aws_s3 CASCADE; 2 | 3 | CREATE TEMP TABLE Headphone_temp (LIKE Headphone); 4 | 5 | CREATE TEMP TABLE InEarMonitor_temp (LIKE InEarMonitor); 6 | 7 | SELECT 8 | aws_s3.table_import_from_s3( 9 | 'Headphone_temp', 10 | '', 11 | '(format csv, header true)', 12 | '{bucket_name}', 13 | 'headphones-silver.csv', 14 | '{region}', 15 | '{access_key}', 16 | '{secret_key}' 17 | ); 18 | 19 | SELECT 20 | aws_s3.table_import_from_s3( 21 | 'InEarMonitor_temp', 22 | '', 23 | '(format csv, header true)', 24 | '{bucket_name}', 25 | 'iems-silver.csv', 26 | '{region}', 27 | '{access_key}', 28 | '{secret_key}' 29 | ); 30 | -------------------------------------------------------------------------------- /airflow/tasks/rds_load/upload_to_rds.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import psycopg2 3 | from dotenv import dotenv_values 4 | 5 | # Load config 6 | configuration_path = pathlib.Path(__file__).parent.parent.resolve() 7 | script_path = pathlib.Path(__file__).parent.resolve() 8 | config = dotenv_values(f"{configuration_path}/configuration.env") 9 | 10 | 11 | def create_conn(): 12 | try: 13 | conn = psycopg2.connect( 14 | host=config["rds_instance_endpoint"].split(":")[0], 15 | port=config["rds_port"], 16 | user=config["rds_username"], 17 | password=config["rds_password"], 18 | dbname=config["rds_database_name"], 19 | ) 20 | 21 | return conn 22 | except Exception as exception: 23 | print(exception) 24 | 25 | 26 | def prepare_query(query_filename: str) -> str: 27 | """ 28 | Helper method to concatenate entire query and help with preparing parameters 29 | 30 | Args: 31 | query_filename (str): name of the sql file containing query 32 | 33 | Returns: 34 | str: completely concatenated query 35 | """ 36 | query_cursor = open(f"{script_path}/queries/{query_filename}.sql", "r") 37 | query = query_cursor.read() 38 | 39 | return query 40 | 41 | 42 | if __name__ == "__main__": 43 | conn = create_conn() 44 | cursor = conn.cursor() 45 | 46 | # Creating tables if not already created 47 | create_tables_query = prepare_query("create_tables") 48 | 49 | # Load from staging to main tables, contains transaction to enable rollbacks 50 | load_temp_tables_query = prepare_query("load_temp_tables").format( 51 | bucket_name=config["bucket_name"], 52 | region=config["aws_region"], 53 | access_key=config["aws_access_key_id"], 54 | secret_key=config["aws_secret_access_key"], 55 | ) 56 | 57 | load_main_tables_query = prepare_query("load_main_tables") 58 | 59 | cursor.execute(create_tables_query) 60 | cursor.execute(load_temp_tables_query) 61 | cursor.execute(load_main_tables_query) 62 | 63 | conn.commit() 64 | cursor.close() 65 | conn.close() 66 | -------------------------------------------------------------------------------- /airflow/tasks/redshift_load/queries/create_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS Headphone ( 2 | Rank_Grade varchar(5) NOT NULL, 3 | Value_Rating int NOT NULL, 4 | Model varchar(500) NOT NULL PRIMARY KEY, 5 | Price int NOT NULL, 6 | Audio_Signature varchar(200) NOT NULL, 7 | Tone_Grade varchar(5) NOT NULL, 8 | Technical_Grade varchar(5) NOT NULL, 9 | Driver_Type varchar(200), 10 | Fit_Cup varchar(200) 11 | ); 12 | 13 | CREATE TABLE IF NOT EXISTS InEarMonitor ( 14 | Rank_Grade varchar(5) NOT NULL, 15 | Value_Rating int NOT NULL, 16 | Model varchar(500) NOT NULL PRIMARY KEY, 17 | Price int NOT NULL, 18 | Audio_Signature varchar(200) NOT NULL, 19 | Tone_Grade varchar(5) NOT NULL, 20 | Technical_Grade varchar(5) NOT NULL, 21 | Driver_Type varchar(200) 22 | ); 23 | -------------------------------------------------------------------------------- /airflow/tasks/redshift_load/queries/load_main_tables.sql: -------------------------------------------------------------------------------- 1 | BEGIN TRANSACTION; 2 | 3 | -- Delete rows through inner join 4 | DELETE FROM 5 | InEarMonitor USING InEarMonitor_Temp 6 | WHERE 7 | InEarMonitor.Model = InEarMonitor_Temp.Model; 8 | 9 | DELETE FROM 10 | Headphone USING Headphone_Temp 11 | WHERE 12 | Headphone.Model = Headphone_Temp.Model; 13 | 14 | -- Move data from staging table to main tables 15 | INSERT INTO 16 | InEarMonitor 17 | SELECT 18 | * 19 | FROM 20 | InEarMonitor_Temp; 21 | 22 | INSERT INTO 23 | Headphone 24 | SELECT 25 | * 26 | FROM 27 | Headphone_Temp; 28 | 29 | END TRANSACTION; 30 | 31 | -- Drop staging tables 32 | DROP TABLE InEarMonitor_Temp; 33 | 34 | DROP TABLE Headphone_Temp; 35 | -------------------------------------------------------------------------------- /airflow/tasks/redshift_load/queries/load_temp_tables.sql: -------------------------------------------------------------------------------- 1 | CREATE TEMP TABLE InEarMonitor_temp (LIKE InEarMonitor); 2 | 3 | CREATE TEMP TABLE Headphone_temp (LIKE Headphone); 4 | 5 | -- Load CSV contents in temporary tables 6 | COPY InEarMonitor_temp 7 | FROM 8 | 's3://{bucket_name}/iems-silver.csv' CREDENTIALS 'aws_access_key_id={aws_access_id};aws_secret_access_key={aws_secret_key}' IGNOREHEADER 1 DELIMITER ',' CSV; 9 | 10 | COPY Headphone_temp 11 | FROM 12 | 's3://{bucket_name}/headphones-silver.csv' CREDENTIALS 'aws_access_key_id={aws_access_id};aws_secret_access_key={aws_secret_key}' IGNOREHEADER 1 DELIMITER ',' CSV; 13 | -------------------------------------------------------------------------------- /airflow/tasks/redshift_load/upload_to_redshift.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import psycopg2 3 | from dotenv import dotenv_values 4 | 5 | # Load config 6 | configuration_path = pathlib.Path(__file__).parent.parent.resolve() 7 | script_path = pathlib.Path(__file__).parent.resolve() 8 | config = dotenv_values(f"{configuration_path}/configuration.env") 9 | 10 | 11 | def create_conn(): 12 | try: 13 | conn = psycopg2.connect( 14 | host=config["redshift_host"].split(":")[0], 15 | port=config["redshift_port"], 16 | user=config["redshift_user"], 17 | password=config["redshift_password"], 18 | dbname=config["redshift_database"], 19 | ) 20 | 21 | return conn 22 | except Exception as exception: 23 | print(exception) 24 | 25 | 26 | def prepare_query(query_filename: str) -> str: 27 | """ 28 | Helper method to concatenate entire query and help with preparing parameters 29 | 30 | Args: 31 | query_filename (str): name of the sql file containing query 32 | 33 | Returns: 34 | str: completely concatenated query 35 | """ 36 | query_cursor = open(f"{script_path}/queries/{query_filename}.sql", "r") 37 | query = query_cursor.read() 38 | 39 | return query 40 | 41 | 42 | if __name__ == "__main__": 43 | conn = create_conn() 44 | cursor = conn.cursor() 45 | 46 | # Creating tables if not already created 47 | create_tables_query = prepare_query("create_tables") 48 | 49 | # Load from staging to main tables, contains transaction to enable rollbacks 50 | load_temp_tables_query = prepare_query("load_temp_tables").format( 51 | bucket_name=config["bucket_name"], 52 | region=config["aws_region"], 53 | aws_access_id=config["aws_access_key_id"], 54 | aws_secret_key=config["aws_secret_access_key"], 55 | ) 56 | 57 | load_main_tables_query = prepare_query("load_main_tables") 58 | 59 | cursor.execute(create_tables_query) 60 | cursor.execute(load_temp_tables_query) 61 | cursor.execute(load_main_tables_query) 62 | 63 | conn.commit() 64 | cursor.close() 65 | conn.close() 66 | -------------------------------------------------------------------------------- /airflow/tasks/scraper_extract/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris-tlp/audiophile-e2e-pipeline/5ab3d132c3b941fb0240a93a6277717a296c341a/airflow/tasks/scraper_extract/__init__.py -------------------------------------------------------------------------------- /airflow/tasks/scraper_extract/models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field, validator 2 | 3 | 4 | def grade_atmost_2_chars(rank: str) -> bool: 5 | """ 6 | Reusable method to validate fields based on the letter grade convention 7 | """ 8 | return len(rank) > 2 9 | 10 | 11 | class InEarMonitor(BaseModel): 12 | """ 13 | Pydantic model containing validators for the schema of InEarMonitors. 14 | """ 15 | 16 | rank: str 17 | model: str 18 | signature: str 19 | tone_grade: str 20 | driver_type: str 21 | technical_grade: str 22 | price: int = Field(-1, ge=-1) # ensure price does not go below -1 23 | value_rating: int = Field(-1, ge=-1, le=3) # ensure rating is from [-1, 3] inclusive 24 | 25 | # Ensure all fields with a convention of letter grades does not exceed two characters 26 | _ensure_rank_max_2_chars = validator("rank", allow_reuse=True)(grade_atmost_2_chars) 27 | _ensure_tonegrade_max_2_chars = validator("tone_grade", allow_reuse=True)(grade_atmost_2_chars) 28 | _ensure_technicalgrade_max_2_chars = validator("technical_grade", allow_reuse=True)( 29 | grade_atmost_2_chars 30 | ) 31 | 32 | @validator("signature") 33 | def ensure_signature_has_no_quotes(cls, signature: str): 34 | """ 35 | Ensure that any signatures after sanitzation do not have quotes 36 | """ 37 | if '"' in signature: 38 | raise ValueError("Signature must not contain quotes") 39 | return signature.title() 40 | 41 | 42 | class Headphone(InEarMonitor): 43 | """ 44 | Pydantic model containing validators for Headphones. 45 | 46 | Args: 47 | InEarMonitor (InEarMonitor): subclasses the InEarMonitor class, including all validators 48 | """ 49 | 50 | fit_cup: str 51 | -------------------------------------------------------------------------------- /airflow/tasks/scraper_extract/scraper.py: -------------------------------------------------------------------------------- 1 | import re 2 | import csv 3 | import pprint 4 | import pathlib 5 | import logging 6 | import requests 7 | from bs4 import BeautifulSoup 8 | 9 | script_path = pathlib.Path(__file__).parent.resolve() 10 | 11 | logging.basicConfig(filename="logs.log", level=logging.INFO) 12 | # truncating log file before new run 13 | with open("logs.log", "w"): 14 | pass 15 | 16 | 17 | class Scraper: 18 | """ 19 | Encapsulates all the logic for the web scraper. 20 | """ 21 | 22 | def __init__(self) -> None: 23 | self.base_url = "https://crinacle.com/rankings/" 24 | 25 | def clean_headers(self, headers: list) -> list: 26 | """ 27 | Formats the table headers in snake case, code friendly format 28 | 29 | Args: 30 | headers (list): Contains the unformatted dirty table headers 31 | 32 | Returns: 33 | list: Returns properly formatted table headers 34 | """ 35 | clean_headers = [] 36 | 37 | for header in headers: 38 | # Remove unnecessary terms 39 | if "(" in header or "/" in header: 40 | header = header.split(" ")[0] 41 | 42 | # Rename header for conformity between both IEMS and headphones 43 | if "Setup" == header: 44 | header = "driver_type" 45 | 46 | # Convert to snake_case 47 | clean_headers.append( 48 | re.sub(r"(?<=[a-z])(?=[A-Z])|[^a-zA-Z]", " ", header).replace(" ", "_").lower() 49 | ) 50 | 51 | return clean_headers 52 | 53 | def scrape(self, device_type: str) -> list: 54 | """ 55 | Scrapes Crinacle's databases containing technical information about Headphones and IEMs. 56 | 57 | Args: 58 | device_type (str): specifies the device type and url to be scraped. 59 | """ 60 | url = self.base_url + device_type 61 | try: 62 | response = requests.get(url) 63 | soup = BeautifulSoup(response.text, "html.parser") 64 | except requests.exceptions.RequestException as e: 65 | logging.log(e) 66 | 67 | # Find all tables within page, in this case, the only one 68 | data_table = soup.findChildren("table")[0] 69 | 70 | # Get all the headers for the table 71 | thead = data_table.find_all("thead", recursive=False) 72 | headers = thead[0].findChildren("th") 73 | headers = [cell.text for cell in headers] 74 | headers = self.clean_headers(headers) 75 | 76 | # Get all rows within the table (excluding links) 77 | tbody = data_table.find_all("tbody", recursive=False) 78 | rows = tbody[0].find_all("tr", recursive=False) 79 | 80 | device_data = [] 81 | 82 | for row in rows: 83 | row_data = {} 84 | for i, cell in enumerate(row.find_all("td", recursive=False)): 85 | row_data[headers[i]] = cell.get_text() 86 | device_data.append(row_data) 87 | 88 | # device_data = self.convert_to_model(device_data=device_data, device_type=device_type) 89 | return device_data 90 | 91 | def convert_to_csv(self, device_data: list, device_type: str, data_level: str) -> None: 92 | """ 93 | Converts a list of dictionaries to a csv file 94 | 95 | Args: 96 | device_data (list[dict]): List of dictionaries containing each device 97 | device_type (str): String specifiying the type of device: headphones or iems 98 | data_level (str): Signifies the level of data, ie, gold, bronze, silver 99 | """ 100 | with open(f"/tmp/{device_type}-{data_level}.csv", "w") as csvfile: 101 | writer = csv.DictWriter(csvfile, fieldnames=device_data[0].keys()) 102 | writer.writeheader() 103 | writer.writerows(device_data) 104 | 105 | 106 | if __name__ == "__main__": 107 | scraper = Scraper() 108 | headphones = scraper.scrape(device_type="headphones") 109 | iems = scraper.scrape(device_type="iems") 110 | 111 | scraper.convert_to_csv(device_data=headphones, device_type="headphones", data_level="bronze") 112 | scraper.convert_to_csv(device_data=iems, device_type="iems", data_level="bronze") 113 | -------------------------------------------------------------------------------- /airflow/tasks/scraper_extract/test_scraper.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import NonCallableMock 2 | import pytest 3 | import requests 4 | import unittest 5 | from bs4 import BeautifulSoup 6 | 7 | 8 | class TestScraper(unittest.TestCase): 9 | """ 10 | Unittests to test the structure of the webpage being scraped 11 | """ 12 | 13 | def setUp(self): 14 | headphone_url = "https://crinacle.com/rankings/headphones/" 15 | iem_url = "https://crinacle.com/rankings/iems/" 16 | 17 | self.headphone_response = requests.get(headphone_url) 18 | self.headphone_soup = BeautifulSoup(self.headphone_response.text, "html.parser") 19 | 20 | self.iem_response = requests.get(iem_url) 21 | self.iem_soup = BeautifulSoup(self.iem_response.text, "html.parser") 22 | 23 | def test_iem_response(self): 24 | """ 25 | Test if IEM url connection is successful 26 | """ 27 | self.assertEqual(self.headphone_response.status_code, 200) 28 | 29 | def test_headphone_response(self): 30 | """ 31 | Test if Headphone url connection is successful 32 | """ 33 | self.assertEqual(self.iem_response.status_code, 200) 34 | 35 | def test_iem_soup(self): 36 | """ 37 | Test if IEM soup parsed successfully 38 | """ 39 | self.assertNotEqual(self.iem_soup, None) 40 | 41 | def test_headphone_soup(self): 42 | """ 43 | Test if Headphone soup parsed successfully 44 | """ 45 | self.assertNotEqual(self.headphone_soup, None) 46 | 47 | def test_iem_table(self): 48 | """ 49 | Test if the database table in the IEM url exists 50 | """ 51 | self.assertGreaterEqual(len(self.iem_soup.find_all("table")), 0) 52 | 53 | def test_headphone_table(self): 54 | """ 55 | Test if the database table in the Headphone url exists 56 | """ 57 | self.assertGreaterEqual(len(self.headphone_soup.find_all("table")), 0) 58 | 59 | def test_iem_table_not_empty(self): 60 | """ 61 | Test if the database table in the IEM url is not empty 62 | """ 63 | self.assertGreaterEqual(len(self.iem_soup.find_all("tr")), 0) 64 | 65 | def test_headphone_table_not_empty(self): 66 | """ 67 | Test if the database table in the Headphone url is not empty 68 | """ 69 | self.assertGreaterEqual(len(self.headphone_soup.find_all("tr")), 0) 70 | 71 | def test_iem_table_header(self): 72 | """ 73 | Test if the header in the IEM database table exists 74 | """ 75 | self.assertNotEqual(self.iem_soup.find_all("thead"), None) 76 | 77 | def test_headphone_table_header(self): 78 | """ 79 | Test if the header in the Headphone database table exists 80 | """ 81 | self.assertNotEqual(self.headphone_soup.find_all("thead"), None) 82 | 83 | def test_iem_table_body(self): 84 | """ 85 | Test if the body in the IEM database table exists 86 | """ 87 | self.assertNotEqual(self.iem_soup.find_all("tbody"), None) 88 | 89 | def test_headphone_table_body(self): 90 | """ 91 | Test if the body in the Headphone database table exists 92 | """ 93 | self.assertNotEqual(self.headphone_soup.find_all("tbody"), None) 94 | -------------------------------------------------------------------------------- /airflow/tasks/upload_to_s3.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import boto3 3 | import pathlib 4 | from dotenv import dotenv_values 5 | from botocore.exceptions import ClientError, NoCredentialsError 6 | 7 | # Load config 8 | script_path = pathlib.Path(__file__).parent.resolve() 9 | config = dotenv_values(f"{script_path}/configuration.env") 10 | 11 | # Get CLI arg for data phase upload, ie, bronze, silver, gold 12 | data_level = sys.argv[1] 13 | files = [f"headphones-{data_level}.csv", f"iems-{data_level}.csv"] 14 | 15 | # Set config variables 16 | AWS_BUCKET = config["bucket_name"] 17 | 18 | 19 | def connect_s3(): 20 | """ 21 | Create a boto3 session and connect to the S3 Resource 22 | 23 | Returns: 24 | connection to the S3 bucket 25 | """ 26 | try: 27 | s3_conn = boto3.resource("s3") 28 | return s3_conn 29 | except NoCredentialsError as e: 30 | raise (e) 31 | 32 | 33 | def upload_csv_s3(): 34 | """ 35 | Upload both CSV files to the S3 bucket 36 | """ 37 | s3_conn = connect_s3() 38 | for file in files: 39 | s3_conn.meta.client.upload_file(Filename=f"/tmp/{file}", Bucket=AWS_BUCKET, Key=file) 40 | 41 | 42 | if __name__ == "__main__": 43 | upload_csv_s3() 44 | -------------------------------------------------------------------------------- /airflow/tasks/utilities.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | 4 | def convert_to_csv(device_data: list, device_type: str, data_level: str) -> None: 5 | """Converts a list of dictionaries to a csv file 6 | 7 | Args: 8 | device_data (list[dict]): List of dictionaries containing each device 9 | device_type (str): String specifiying the type of device: headphones or iems 10 | data_level (str): Signifies the level of data, ie, gold, bronze, silver 11 | """ 12 | with open(f"/tmp/{device_type}-{data_level}.csv", "w") as csvfile: 13 | writer = csv.DictWriter(csvfile, fieldnames=device_data[0].keys()) 14 | writer.writeheader() 15 | writer.writerows(device_data) 16 | -------------------------------------------------------------------------------- /airflow/tasks/validate_sanitize_bronze.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pandas as pd 3 | from typing import List 4 | from csv import DictReader 5 | from pydantic import ValidationError 6 | from utilities import convert_to_csv 7 | from scraper_extract.models import InEarMonitor, Headphone 8 | 9 | 10 | def read_csv_as_dicts(filename: str) -> List[dict]: 11 | """ 12 | Returns a list of dictionaries read from specified csv 13 | 14 | Args: 15 | filename (str): name of file to be read 16 | 17 | Returns: 18 | List[dict] 19 | """ 20 | try: 21 | with open(filename, "r") as file: 22 | reader = DictReader(file) 23 | return list(reader) 24 | except OSError as exception: 25 | print(f"{filename} - {exception}") 26 | 27 | 28 | def sanitize_data(data: List[dict]) -> List[dict]: 29 | """ 30 | Performs rudimentary sanitizations on bronze data 31 | 32 | Args: 33 | data (List[dict]): list of IEMs/Headphones 34 | 35 | Returns: 36 | List[dict]: Sanitized data 37 | """ 38 | df = pd.DataFrame(data) 39 | 40 | columns_to_drop = [ 41 | "comments", 42 | "based_on", 43 | "note_weight", 44 | "pricesort", 45 | "techsort", 46 | "tonesort", 47 | "ranksort" 48 | ] 49 | 50 | df = df.drop(columns_to_drop, axis=1) 51 | 52 | # Some signatures have quotes around them, unneeded 53 | df["signature"] = df["signature"].str.replace('"', "") 54 | 55 | for index, row in df.iterrows(): 56 | # Replacing discontinued devices with no price with -1 57 | if re.search("Discont", str(row["price"])): 58 | row["price"] = -1 59 | 60 | # Replacing ? device prices with -1 61 | if re.search("\\?", str(row["price"])): 62 | row["price"] = -1 63 | 64 | # Some prices have models embedded to them, this replaces with only price 65 | # Ex: 3000(HE1000) gives 3000 66 | if re.search("[a-zA-Z]", str(row["price"])): 67 | row["price"] = list(filter(None, re.split(r"(\d+)", str(row["price"]))))[0] 68 | 69 | # Some are still text even after splits and earlier cleanses 70 | if re.search("[a-zA-Z]", str(row["price"])): 71 | row["price"] = -1 72 | 73 | # Replace star text rating with number. If no stars, replace with -1 74 | row["value_rating"] = len(row["value_rating"]) if row["value_rating"] else -1 75 | 76 | return df.to_dict("records") 77 | 78 | 79 | if __name__ == "__main__": 80 | headphones_file = "/tmp/headphones-bronze.csv" 81 | iems_file = "/tmp/iems-bronze.csv" 82 | 83 | iems_list = read_csv_as_dicts(iems_file) 84 | headphones_list = read_csv_as_dicts(headphones_file) 85 | 86 | # Sanitize both CSV files with similar parameters 87 | iems_list_sanitized = sanitize_data(iems_list) 88 | headphones_list_sanitized = sanitize_data(headphones_list) 89 | 90 | # Validates all headphones/iems in a list based on the validators 91 | # defined in the respective PyDantic models 92 | try: 93 | iems_list = [InEarMonitor.parse_obj(iem) for iem in iems_list_sanitized] 94 | except ValidationError as exception: 95 | print(f"IEM - {exception}") 96 | 97 | try: 98 | headphones_list = [ 99 | Headphone.parse_obj(headphone) for headphone in headphones_list_sanitized 100 | ] 101 | except ValidationError as exception: 102 | print(f"Headphone - {exception}") 103 | 104 | convert_to_csv(device_data=iems_list_sanitized, device_type="iems", data_level="silver") 105 | convert_to_csv( 106 | device_data=headphones_list_sanitized, device_type="headphones", data_level="silver" 107 | ) 108 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | # 18 | 19 | # Basic Airflow cluster configuration for CeleryExecutor with Redis and PostgreSQL. 20 | # 21 | # WARNING: This configuration is for local development. Do not use it in a production deployment. 22 | # 23 | # This configuration supports basic configuration using environment variables or an .env file 24 | # The following variables are supported: 25 | # 26 | # AIRFLOW_IMAGE_NAME - Docker image name used to run Airflow. 27 | # Default: apache/airflow:2.4.1 28 | # AIRFLOW_UID - User ID in Airflow containers 29 | # Default: 50000 30 | # Those configurations are useful mostly in case of standalone testing/running Airflow in test/try-out mode 31 | # 32 | # _AIRFLOW_WWW_USER_USERNAME - Username for the administrator account (if requested). 33 | # Default: airflow 34 | # _AIRFLOW_WWW_USER_PASSWORD - Password for the administrator account (if requested). 35 | # Default: airflow 36 | # _PIP_ADDITIONAL_REQUIREMENTS - Additional PIP requirements to add when starting all containers. 37 | # Default: '' 38 | # 39 | # Feel free to modify this file to suit your needs. 40 | --- 41 | version: '3' 42 | x-airflow-common: 43 | &airflow-common 44 | # In order to add custom dependencies or upgrade provider packages you can use your extended image. 45 | # Comment the image line, place your Dockerfile in the directory where you placed the docker-compose.yaml 46 | # and uncomment the "build" line below, Then run `docker-compose build` to build the images. 47 | # image: ${AIRFLOW_IMAGE_NAME:-apache/airflow:2.4.1} 48 | image: ${AIRFLOW_IMAGE_NAME:-airflow-extended:latest} 49 | # build: . 50 | environment: 51 | &airflow-common-env 52 | AIRFLOW__CORE__EXECUTOR: CeleryExecutor 53 | AIRFLOW__DATABASE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow 54 | # For backward compatibility, with Airflow <2.3 55 | AIRFLOW__CORE__SQL_ALCHEMY_CONN: postgresql+psycopg2://airflow:airflow@postgres/airflow 56 | AIRFLOW__CELERY__RESULT_BACKEND: db+postgresql://airflow:airflow@postgres/airflow 57 | AIRFLOW__CELERY__BROKER_URL: redis://:@redis:6379/0 58 | AIRFLOW__CORE__FERNET_KEY: '' 59 | AIRFLOW__CORE__DAGS_ARE_PAUSED_AT_CREATION: 'true' 60 | AIRFLOW__CORE__LOAD_EXAMPLES: 'true' 61 | AIRFLOW__API__AUTH_BACKENDS: 'airflow.api.auth.backend.basic_auth' 62 | _PIP_ADDITIONAL_REQUIREMENTS: ${_PIP_ADDITIONAL_REQUIREMENTS:-} 63 | volumes: 64 | - ./airflow/dags:/opt/airflow/dags 65 | - ./airflow/logs:/opt/airflow/logs 66 | - ./airflow/plugins:/opt/airflow/plugins 67 | - ./airflow/tasks:/opt/airflow/tasks 68 | - $HOME/.aws/credentials:/home/airflow/.aws/credentials:ro 69 | depends_on: 70 | &airflow-common-depends-on 71 | redis: 72 | condition: service_healthy 73 | postgres: 74 | condition: service_healthy 75 | 76 | services: 77 | postgres: 78 | image: postgres:13 79 | environment: 80 | POSTGRES_USER: airflow 81 | POSTGRES_PASSWORD: airflow 82 | POSTGRES_DB: airflow 83 | volumes: 84 | - postgres-db-volume:/var/lib/postgresql/data 85 | healthcheck: 86 | test: ["CMD", "pg_isready", "-U", "airflow"] 87 | interval: 5s 88 | retries: 5 89 | restart: always 90 | 91 | redis: 92 | image: redis:latest 93 | expose: 94 | - 6379 95 | healthcheck: 96 | test: ["CMD", "redis-cli", "ping"] 97 | interval: 5s 98 | timeout: 30s 99 | retries: 50 100 | restart: always 101 | 102 | airflow-webserver: 103 | <<: *airflow-common 104 | command: webserver 105 | ports: 106 | - 8080:8080 107 | healthcheck: 108 | test: ["CMD", "curl", "--fail", "http://localhost:8080/health"] 109 | interval: 10s 110 | timeout: 10s 111 | retries: 5 112 | restart: always 113 | depends_on: 114 | <<: *airflow-common-depends-on 115 | airflow-init: 116 | condition: service_completed_successfully 117 | 118 | airflow-scheduler: 119 | <<: *airflow-common 120 | command: scheduler 121 | healthcheck: 122 | test: ["CMD-SHELL", 'airflow jobs check --job-type SchedulerJob --hostname "$${HOSTNAME}"'] 123 | interval: 10s 124 | timeout: 10s 125 | retries: 5 126 | restart: always 127 | depends_on: 128 | <<: *airflow-common-depends-on 129 | airflow-init: 130 | condition: service_completed_successfully 131 | 132 | airflow-worker: 133 | <<: *airflow-common 134 | command: celery worker 135 | healthcheck: 136 | test: 137 | - "CMD-SHELL" 138 | - 'celery --app airflow.executors.celery_executor.app inspect ping -d "celery@$${HOSTNAME}"' 139 | interval: 10s 140 | timeout: 10s 141 | retries: 5 142 | environment: 143 | <<: *airflow-common-env 144 | # Required to handle warm shutdown of the celery workers properly 145 | # See https://airflow.apache.org/docs/docker-stack/entrypoint.html#signal-propagation 146 | DUMB_INIT_SETSID: "0" 147 | restart: always 148 | depends_on: 149 | <<: *airflow-common-depends-on 150 | airflow-init: 151 | condition: service_completed_successfully 152 | 153 | airflow-triggerer: 154 | <<: *airflow-common 155 | command: triggerer 156 | healthcheck: 157 | test: ["CMD-SHELL", 'airflow jobs check --job-type TriggererJob --hostname "$${HOSTNAME}"'] 158 | interval: 10s 159 | timeout: 10s 160 | retries: 5 161 | restart: always 162 | depends_on: 163 | <<: *airflow-common-depends-on 164 | airflow-init: 165 | condition: service_completed_successfully 166 | 167 | airflow-init: 168 | <<: *airflow-common 169 | entrypoint: /bin/bash 170 | # yamllint disable rule:line-length 171 | command: 172 | - -c 173 | - | 174 | function ver() { 175 | printf "%04d%04d%04d%04d" $${1//./ } 176 | } 177 | airflow_version=$$(AIRFLOW__LOGGING__LOGGING_LEVEL=INFO && gosu airflow airflow version) 178 | airflow_version_comparable=$$(ver $${airflow_version}) 179 | min_airflow_version=2.2.0 180 | min_airflow_version_comparable=$$(ver $${min_airflow_version}) 181 | if (( airflow_version_comparable < min_airflow_version_comparable )); then 182 | echo 183 | echo -e "\033[1;31mERROR!!!: Too old Airflow version $${airflow_version}!\e[0m" 184 | echo "The minimum Airflow version supported: $${min_airflow_version}. Only use this or higher!" 185 | echo 186 | exit 1 187 | fi 188 | if [[ -z "${AIRFLOW_UID}" ]]; then 189 | echo 190 | echo -e "\033[1;33mWARNING!!!: AIRFLOW_UID not set!\e[0m" 191 | echo "If you are on Linux, you SHOULD follow the instructions below to set " 192 | echo "AIRFLOW_UID environment variable, otherwise files will be owned by root." 193 | echo "For other operating systems you can get rid of the warning with manually created .env file:" 194 | echo " See: https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#setting-the-right-airflow-user" 195 | echo 196 | fi 197 | one_meg=1048576 198 | mem_available=$$(($$(getconf _PHYS_PAGES) * $$(getconf PAGE_SIZE) / one_meg)) 199 | cpus_available=$$(grep -cE 'cpu[0-9]+' /proc/stat) 200 | disk_available=$$(df / | tail -1 | awk '{print $$4}') 201 | warning_resources="false" 202 | if (( mem_available < 4000 )) ; then 203 | echo 204 | echo -e "\033[1;33mWARNING!!!: Not enough memory available for Docker.\e[0m" 205 | echo "At least 4GB of memory required. You have $$(numfmt --to iec $$((mem_available * one_meg)))" 206 | echo 207 | warning_resources="true" 208 | fi 209 | if (( cpus_available < 2 )); then 210 | echo 211 | echo -e "\033[1;33mWARNING!!!: Not enough CPUS available for Docker.\e[0m" 212 | echo "At least 2 CPUs recommended. You have $${cpus_available}" 213 | echo 214 | warning_resources="true" 215 | fi 216 | if (( disk_available < one_meg * 10 )); then 217 | echo 218 | echo -e "\033[1;33mWARNING!!!: Not enough Disk space available for Docker.\e[0m" 219 | echo "At least 10 GBs recommended. You have $$(numfmt --to iec $$((disk_available * 1024 )))" 220 | echo 221 | warning_resources="true" 222 | fi 223 | if [[ $${warning_resources} == "true" ]]; then 224 | echo 225 | echo -e "\033[1;33mWARNING!!!: You have not enough resources to run Airflow (see above)!\e[0m" 226 | echo "Please follow the instructions to increase amount of resources available:" 227 | echo " https://airflow.apache.org/docs/apache-airflow/stable/howto/docker-compose/index.html#before-you-begin" 228 | echo 229 | fi 230 | mkdir -p /sources/logs /sources/dags /sources/plugins 231 | chown -R "${AIRFLOW_UID}:0" /sources/{logs,dags,plugins} 232 | exec /entrypoint airflow version 233 | # yamllint enable rule:line-length 234 | environment: 235 | <<: *airflow-common-env 236 | _AIRFLOW_DB_UPGRADE: 'true' 237 | _AIRFLOW_WWW_USER_CREATE: 'true' 238 | _AIRFLOW_WWW_USER_USERNAME: ${_AIRFLOW_WWW_USER_USERNAME:-airflow} 239 | _AIRFLOW_WWW_USER_PASSWORD: ${_AIRFLOW_WWW_USER_PASSWORD:-airflow} 240 | _PIP_ADDITIONAL_REQUIREMENTS: '' 241 | user: "0:0" 242 | volumes: 243 | - ./airflow/:/sources 244 | 245 | 246 | airflow-cli: 247 | <<: *airflow-common 248 | profiles: 249 | - debug 250 | environment: 251 | <<: *airflow-common-env 252 | CONNECTION_CHECK_MAX_COUNT: "0" 253 | # Workaround for entrypoint issue. See: https://github.com/apache/airflow/issues/16252 254 | command: 255 | - bash 256 | - -c 257 | - airflow 258 | 259 | # You can enable flower by adding "--profile flower" option e.g. docker-compose --profile flower up 260 | # or by explicitly targeted on the command line e.g. docker-compose up flower. 261 | # See: https://docs.docker.com/compose/profiles/ 262 | flower: 263 | <<: *airflow-common 264 | command: celery flower 265 | profiles: 266 | - flower 267 | ports: 268 | - 5555:5555 269 | healthcheck: 270 | test: ["CMD", "curl", "--fail", "http://localhost:5555/"] 271 | interval: 10s 272 | timeout: 10s 273 | retries: 5 274 | restart: always 275 | depends_on: 276 | <<: *airflow-common-depends-on 277 | airflow-init: 278 | condition: service_completed_successfully 279 | 280 | volumes: 281 | postgres-db-volume: 282 | -------------------------------------------------------------------------------- /generate_config.sh: -------------------------------------------------------------------------------- 1 | cd terraform/ 2 | terraform output > ../airflow/tasks/configuration.env 3 | cat $HOME/.aws/credentials >> ../airflow/tasks/configuration.env 4 | -------------------------------------------------------------------------------- /images/architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris-tlp/audiophile-e2e-pipeline/5ab3d132c3b941fb0240a93a6277717a296c341a/images/architecture.jpeg -------------------------------------------------------------------------------- /images/metabase_dashboard.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ris-tlp/audiophile-e2e-pipeline/5ab3d132c3b941fb0240a93a6277717a296c341a/images/metabase_dashboard.jpeg -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.black] 2 | line-length = 100 3 | -------------------------------------------------------------------------------- /terraform/budget.tf: -------------------------------------------------------------------------------- 1 | resource "aws_budgets_budget" "monthly-budget" { 2 | name = "monthly-budget" 3 | budget_type = "COST" 4 | limit_amount = "20" 5 | limit_unit = "USD" 6 | time_period_start = "2022-09-01_00:00" 7 | time_unit = "MONTHLY" 8 | 9 | notification { 10 | comparison_operator = "GREATER_THAN" 11 | threshold = 100 12 | threshold_type = "PERCENTAGE" 13 | notification_type = "FORECASTED" 14 | subscriber_email_addresses = ["omarkhantlp@gmail.com"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /terraform/output.tf: -------------------------------------------------------------------------------- 1 | # Output Region set for AWS 2 | output "aws_region" { 3 | description = "Region set for AWS" 4 | value = var.aws_region 5 | } 6 | 7 | output "bucket_name" { 8 | description = "S3 bucket name." 9 | value = aws_s3_bucket.audiophile-bucket.id 10 | } 11 | 12 | output "redshift_password" { 13 | description = "Password for the database in the Redshift cluster" 14 | value = var.redshift_password 15 | } 16 | 17 | output "redshift_user" { 18 | description = "Username for the database in the Redshift cluster" 19 | value = aws_redshift_cluster.audiophile_cluster.master_username 20 | } 21 | 22 | output "redshift_port" { 23 | description = "Port of the database in the Redshift cluster" 24 | value = aws_redshift_cluster.audiophile_cluster.port 25 | } 26 | 27 | output "redshift_host" { 28 | description = "Host to connect to the Redshift cluster" 29 | value = aws_redshift_cluster.audiophile_cluster.endpoint 30 | } 31 | 32 | output "redshift_database" { 33 | description = "Database name in the Redshift cluster" 34 | value = aws_redshift_cluster.audiophile_cluster.database_name 35 | } 36 | 37 | output "rds_database_name" { 38 | description = "Database name in the RDS cluster" 39 | value = aws_db_instance.rds_instance.db_name 40 | } 41 | 42 | output "rds_instance_endpoint" { 43 | description = "Endpoint of the RDS cluster" 44 | value = aws_db_instance.rds_instance.endpoint 45 | } 46 | 47 | output "rds_username" { 48 | description = "Username of the RDS cluster" 49 | value = aws_db_instance.rds_instance.username 50 | } 51 | 52 | output "rds_password" { 53 | description = "Password for the RDS cluster" 54 | value = var.rds_password 55 | } 56 | 57 | output "rds_port" { 58 | description = "Port for the RDS cluster" 59 | value = aws_db_instance.rds_instance.port 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /terraform/provider.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | aws = { 4 | source = "hashicorp/aws" 5 | version = "~>4.0" 6 | } 7 | } 8 | } 9 | 10 | provider "aws" { 11 | region = var.aws_region 12 | } 13 | -------------------------------------------------------------------------------- /terraform/rds.tf: -------------------------------------------------------------------------------- 1 | resource "aws_db_instance" "rds_instance" { 2 | allocated_storage = 20 3 | identifier = "rds-terraform" 4 | instance_class = "db.t3.micro" 5 | engine = "postgres" 6 | engine_version = "14.1" 7 | 8 | db_name = "crinacle_audiophile_ratings" 9 | username = "awsadmin" 10 | password = var.rds_password 11 | 12 | 13 | publicly_accessible = true 14 | skip_final_snapshot = true 15 | vpc_security_group_ids = [aws_security_group.rds_security_group.id] 16 | 17 | } 18 | 19 | resource "aws_security_group" "rds_security_group" { 20 | name = "rds_security_group" 21 | ingress { 22 | from_port = 0 23 | to_port = 0 24 | protocol = "-1" 25 | cidr_blocks = ["0.0.0.0/0"] 26 | } 27 | egress { 28 | from_port = 0 29 | to_port = 0 30 | protocol = "-1" 31 | cidr_blocks = ["0.0.0.0/0"] 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /terraform/redshift.tf: -------------------------------------------------------------------------------- 1 | resource "aws_redshift_cluster" "audiophile_cluster" { 2 | cluster_identifier = "tf-redshift-cluster" 3 | database_name = "crinacle_audiophile_ratings" 4 | master_username = "admin" 5 | master_password = var.redshift_password 6 | node_type = "dc2.large" 7 | cluster_type = "single-node" 8 | 9 | skip_final_snapshot = true 10 | publicly_accessible = true 11 | automated_snapshot_retention_period = 0 12 | vpc_security_group_ids = [aws_security_group.redshift_security_group.id] 13 | } 14 | 15 | resource "aws_security_group" "redshift_security_group" { 16 | name = "redshift_security_group" 17 | ingress { 18 | from_port = 0 19 | to_port = 0 20 | protocol = "-1" 21 | cidr_blocks = ["0.0.0.0/0"] 22 | } 23 | egress { 24 | from_port = 0 25 | to_port = 0 26 | protocol = "-1" 27 | cidr_blocks = ["0.0.0.0/0"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /terraform/s3.tf: -------------------------------------------------------------------------------- 1 | # Create S3 bucket with a specific prefix 2 | resource "aws_s3_bucket" "audiophile-bucket" { 3 | bucket_prefix = var.bucket_prefix 4 | force_destroy = true 5 | } 6 | 7 | # Enable versioning for S3 bucket 8 | resource "aws_s3_bucket_versioning" "audiophile-bucket-versioning" { 9 | bucket = aws_s3_bucket.audiophile-bucket.id 10 | versioning_configuration { 11 | status = var.versioning 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /terraform/variable.tf: -------------------------------------------------------------------------------- 1 | variable "aws_region" { 2 | description = "Region for the AWS services to run in." 3 | type = string 4 | default = "us-east-1" 5 | } 6 | 7 | variable "bucket_prefix" { 8 | description = "Bucket prefix for the S3" 9 | type = string 10 | default = "audiophile-e2e-pipeline-" 11 | } 12 | 13 | variable "rds_password" { 14 | description = "Password for the database in the RDS cluster" 15 | type = string 16 | } 17 | 18 | variable "redshift_password" { 19 | description = "Password for the database in the Redshift cluster" 20 | type = string 21 | } 22 | 23 | variable "versioning" { 24 | type = string 25 | default = "Enabled" 26 | } 27 | --------------------------------------------------------------------------------