From 7c75865859d768f9bf3197be367d47004e70587b Mon Sep 17 00:00:00 2001 From: Ruidy Date: Wed, 7 Jul 2021 12:01:59 +0200 Subject: [PATCH] example --- .coveragerc | 13 + .env.example | 1 + .gitignore | 165 ++++ CONTRIBUTING.md | 90 ++ Makefile | 18 + Pipfile | 41 + Pipfile.lock | 998 ++++++++++++++++++++++ README.md | 97 +++ app/__init__.py | 0 app/__main__.py | 5 + app/core/__init__.py | 0 app/core/app.py | 19 + app/core/config.py | 23 + app/core/graphql.py | 20 + app/database/__init__.py | 0 app/database/file.txt | 1 + app/file/__init__.py | 0 app/file/mutations/__init__.py | 1 + app/file/mutations/upload.py | 20 + app/main.py | 3 + app/schema/__init__.py | 3 + app/schema/mutation.graphql | 6 + app/schema/mutation.py | 6 + app/schema/query.graphql | 3 + app/schema/query.py | 10 + app/schema/schema.py | 3 + app/schema/tests/__init__.py | 0 app/schema/tests/test_graphql_schema.py | 12 + app/schema/upload.graphql | 1 + docs/graphcool_grammer.png | Bin 0 -> 53568 bytes setup.cfg | 18 + tests/__init__.py | 0 tests/functional_api/__init__.py | 0 tests/functional_api/client.py | 34 + tests/functional_api/conftest.py | 16 + tests/functional_api/test_upload_image.py | 30 + 36 files changed, 1657 insertions(+) create mode 100644 .coveragerc create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 Makefile create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 README.md create mode 100644 app/__init__.py create mode 100644 app/__main__.py create mode 100644 app/core/__init__.py create mode 100644 app/core/app.py create mode 100644 app/core/config.py create mode 100644 app/core/graphql.py create mode 100644 app/database/__init__.py create mode 100644 app/database/file.txt create mode 100644 app/file/__init__.py create mode 100644 app/file/mutations/__init__.py create mode 100644 app/file/mutations/upload.py create mode 100644 app/main.py create mode 100644 app/schema/__init__.py create mode 100644 app/schema/mutation.graphql create mode 100644 app/schema/mutation.py create mode 100644 app/schema/query.graphql create mode 100644 app/schema/query.py create mode 100644 app/schema/schema.py create mode 100644 app/schema/tests/__init__.py create mode 100644 app/schema/tests/test_graphql_schema.py create mode 100644 app/schema/upload.graphql create mode 100644 docs/graphcool_grammer.png create mode 100644 setup.cfg create mode 100644 tests/__init__.py create mode 100644 tests/functional_api/__init__.py create mode 100644 tests/functional_api/client.py create mode 100644 tests/functional_api/conftest.py create mode 100644 tests/functional_api/test_upload_image.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..f2a46f4 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,13 @@ +[run] +omit = + # omit tests + */tests/* + # omit non testable files + app/core/* + app/database/* + app/migrations/* + app/schema/* + app/main.py + # omit schema types and controllers +[html] +directory = .htmlcov diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..147ae0a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +DEBUG=True diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..446c7c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ +# Created by .ignore support plugin (hsz.mobi) +### Python template +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +.htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +.idea/ + +# CMake +cmake-build-*/ + +# File-based project format +*.iws + +# IntelliJ +out/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +.vscode/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d7b71da --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,90 @@ +# Contributing + +When contributing to this repository, please first discuss the change you wish to make via issue, +email, or any other communication method with the owners of this repository before making a change. + +Please note we have a code of conduct, please follow it in all your interactions with the project. + +## Merge Request Process + +1. Ensure any install or build dependencies are removed before the end of the layer when doing a + build. +2. Update the [README.md](README.md) with details of changes to the interface, this includes new environment + variables, exposed ports, useful file locations and container parameters. +3. You may merge the Merge Request in once your contribution has been approved by another developer, or if you do not + have permission to do that, you may request the reviewer to merge it for you. + +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the company +- Showing empathy towards other team members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic + address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the [project team](mailto:ruidy.nemausat@gmail.com). All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0f5c92f --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +.PHONY: test +test: + pipenv run pytest -v + # pipenv run pytest --cov=. --cov-report=html --cov-report=xml + # pipenv run diff-cover coverage.xml --compare-branch=origin/development + +.PHONY: run +run: + pipenv run uvicorn app.main:app --reload --port=8000 + +.PHONY: lint +lint: + pipenv run black -l 99 . + pipenv run flake8 . + pipenv run mypy . + pipenv run bandit -r --exclude=test . + pipenv run vulture . + pipenv run safety check diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..413ca98 --- /dev/null +++ b/Pipfile @@ -0,0 +1,41 @@ +[scripts] +dev = "uvicorn app.main:app --reload --port=8000" +test = "python -m pytest --ignore=tests/functional_api" +test-all = "python -m pytest --cov=. --cov-report=html --cov-report=xml" +db-revision = "alembic revision --autogenerate" +db-up = "alembic upgrade head" +db-down = "alembic downgrade -1" + +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] +bandit = "==1.7.0" +black = "==21.6b0" +diff-cover = "==5.0.1" +faker = "==8.1.0" +flake8 = "==3.9.2" +flake8-print = "==4.0.0" +freezegun = "==1.1.0" +mypy = "==0.910" +pytest = "==6.2.4" +pytest-asyncio = "==0.15.0" +pytest-cov = "==2.12.1" +safety = "==1.10.3" +vulture = "==2.3" +types-requests = "*" + +[packages] +ariadne = {extras = ["asgi-file-uploads"], version = "==0.13.0"} +fastapi = "==0.66.0" +pydantic = {extras = ["email"], version = "==1.8.2"} +uvicorn = {extras = ["standard"], version = "==0.13.4"} +aiogqlc = "*" + +[requires] +python_version = "3.9" + +[pipenv] +allow_prereleases = true diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..ef127d7 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,998 @@ +{ + "_meta": { + "hash": { + "sha256": "bdc58fad172db786f44b5db25afda49a9278a82a5fbd950a9015d64d7c2f0db6" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aiogqlc": { + "hashes": [ + "sha256:7981c7ce2f3c8cdec74539c99a7116e647d96861aec56ba5a7903499bc0638c7", + "sha256:9a74b4e0e86067688fc9ed0b17a3ac32f44cd2529d4dd3e3b0ede085fc8ab1ab" + ], + "index": "pypi", + "version": "==2.2.0" + }, + "aiohttp": { + "hashes": [ + "sha256:02f46fc0e3c5ac58b80d4d56eb0a7c7d97fcef69ace9326289fb9f1955e65cfe", + "sha256:0563c1b3826945eecd62186f3f5c7d31abb7391fedc893b7e2b26303b5a9f3fe", + "sha256:114b281e4d68302a324dd33abb04778e8557d88947875cbf4e842c2c01a030c5", + "sha256:14762875b22d0055f05d12abc7f7d61d5fd4fe4642ce1a249abdf8c700bf1fd8", + "sha256:15492a6368d985b76a2a5fdd2166cddfea5d24e69eefed4630cbaae5c81d89bd", + "sha256:17c073de315745a1510393a96e680d20af8e67e324f70b42accbd4cb3315c9fb", + "sha256:209b4a8ee987eccc91e2bd3ac36adee0e53a5970b8ac52c273f7f8fd4872c94c", + "sha256:230a8f7e24298dea47659251abc0fd8b3c4e38a664c59d4b89cca7f6c09c9e87", + "sha256:2e19413bf84934d651344783c9f5e22dee452e251cfd220ebadbed2d9931dbf0", + "sha256:393f389841e8f2dfc86f774ad22f00923fdee66d238af89b70ea314c4aefd290", + "sha256:3cf75f7cdc2397ed4442594b935a11ed5569961333d49b7539ea741be2cc79d5", + "sha256:3d78619672183be860b96ed96f533046ec97ca067fd46ac1f6a09cd9b7484287", + "sha256:40eced07f07a9e60e825554a31f923e8d3997cfc7fb31dbc1328c70826e04cde", + "sha256:493d3299ebe5f5a7c66b9819eacdcfbbaaf1a8e84911ddffcdc48888497afecf", + "sha256:4b302b45040890cea949ad092479e01ba25911a15e648429c7c5aae9650c67a8", + "sha256:515dfef7f869a0feb2afee66b957cc7bbe9ad0cdee45aec7fdc623f4ecd4fb16", + "sha256:547da6cacac20666422d4882cfcd51298d45f7ccb60a04ec27424d2f36ba3eaf", + "sha256:5df68496d19f849921f05f14f31bd6ef53ad4b00245da3195048c69934521809", + "sha256:64322071e046020e8797117b3658b9c2f80e3267daec409b350b6a7a05041213", + "sha256:7615dab56bb07bff74bc865307aeb89a8bfd9941d2ef9d817b9436da3a0ea54f", + "sha256:79ebfc238612123a713a457d92afb4096e2148be17df6c50fb9bf7a81c2f8013", + "sha256:7b18b97cf8ee5452fa5f4e3af95d01d84d86d32c5e2bfa260cf041749d66360b", + "sha256:932bb1ea39a54e9ea27fc9232163059a0b8855256f4052e776357ad9add6f1c9", + "sha256:a00bb73540af068ca7390e636c01cbc4f644961896fa9363154ff43fd37af2f5", + "sha256:a5ca29ee66f8343ed336816c553e82d6cade48a3ad702b9ffa6125d187e2dedb", + "sha256:af9aa9ef5ba1fd5b8c948bb11f44891968ab30356d65fd0cc6707d989cd521df", + "sha256:bb437315738aa441251214dad17428cafda9cdc9729499f1d6001748e1d432f4", + "sha256:bdb230b4943891321e06fc7def63c7aace16095be7d9cf3b1e01be2f10fba439", + "sha256:c6e9dcb4cb338d91a73f178d866d051efe7c62a7166653a91e7d9fb18274058f", + "sha256:cffe3ab27871bc3ea47df5d8f7013945712c46a3cc5a95b6bee15887f1675c22", + "sha256:d012ad7911653a906425d8473a1465caa9f8dea7fcf07b6d870397b774ea7c0f", + "sha256:d9e13b33afd39ddeb377eff2c1c4f00544e191e1d1dee5b6c51ddee8ea6f0cf5", + "sha256:e4b2b334e68b18ac9817d828ba44d8fcb391f6acb398bcc5062b14b2cbeac970", + "sha256:e54962802d4b8b18b6207d4a927032826af39395a3bd9196a5af43fc4e60b009", + "sha256:f705e12750171c0ab4ef2a3c76b9a4024a62c4103e3a55dd6f99265b9bc6fcfc", + "sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a", + "sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95" + ], + "markers": "python_version >= '3.6'", + "version": "==3.7.4.post0" + }, + "ariadne": { + "extras": [ + "asgi-file-uploads" + ], + "hashes": [ + "sha256:56bc3609a0512920f06e9312f8ea6db3c8e4a7cd77f31fbed388f5dba6d589c0", + "sha256:e00abd7eb5869b59a638f1e3a7743445bf387236048cf1b0eb9d7c506dcd37c5" + ], + "index": "pypi", + "version": "==0.13.0" + }, + "async-timeout": { + "hashes": [ + "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", + "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3" + ], + "markers": "python_full_version >= '3.5.3'", + "version": "==3.0.1" + }, + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "chardet": { + "hashes": [ + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "dnspython": { + "hashes": [ + "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216", + "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4" + ], + "markers": "python_version >= '3.6'", + "version": "==2.1.0" + }, + "email-validator": { + "hashes": [ + "sha256:5675c8ceb7106a37e40e2698a57c056756bf3f272cfa8682a4f87ebd95d8440b", + "sha256:aa237a65f6f4da067119b7df3f13e89c25c051327b2b5b66dc075f33d62480d7" + ], + "version": "==1.1.3" + }, + "fastapi": { + "hashes": [ + "sha256:6ea4225448786f3d6fae737713789f87631a7455f65580de0a4a2e50471060d9", + "sha256:85d8aee8c3c46171f4cb7bb3651425a42c07cb9183345d100ef55d88ca2ce15f" + ], + "index": "pypi", + "version": "==0.66.0" + }, + "graphql-core": { + "hashes": [ + "sha256:91d96ef0e86665777bb7115d3bbb6b0326f43dc7dbcdd60da5486a27a50cfb11", + "sha256:a755635d1d364a17e8d270347000722351aaa03f1ab7d280878aae82fc68b1f3" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==3.1.5" + }, + "h11": { + "hashes": [ + "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6", + "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" + ], + "markers": "python_version >= '3.6'", + "version": "==0.12.0" + }, + "httptools": { + "hashes": [ + "sha256:07659649fe6b3948b6490825f89abe5eb1cec79ebfaaa0b4bf30f3f33f3c2ba8", + "sha256:08b79e09114e6ab5c3dbf560bba2cb2257ea38cdaeaf99b7cb80d8f92622fcd9", + "sha256:1e35aa179b67086cc600a984924a88589b90793c9c1b260152ca4908786e09df", + "sha256:31629e1f1b89959f8c0927bad12184dc07977dcf71e24f4772934aa490aa199b", + "sha256:851026bd63ec0af7e7592890d97d15c92b62d9e17094353f19a52c8e2b33710a", + "sha256:8fcca4b7efe353b13a24017211334c57d055a6e132c7adffed13a10d28efca57", + "sha256:9abd788465aa46a0f288bd3a99e53edd184177d6379e2098fd6097bb359ad9d6", + "sha256:aebdf0bd7bf7c90ae6b3be458692bf6e9e5b610b501f9f74c7979015a51db4c4", + "sha256:bda99a5723e7eab355ce57435c70853fc137a65aebf2f1cd4d15d96e2956da7b", + "sha256:c1c63d860749841024951b0a78e4dec6f543d23751ef061d6ab60064c7b8b524", + "sha256:c4111a0a8a00eff1e495d43ea5230aaf64968a48ddba8ea2d5f982efae827404", + "sha256:dce59ee45dd6ee6c434346a5ac527c44014326f560866b4b2f414a692ee1aca8", + "sha256:f759717ca1b2ef498c67ba4169c2b33eecf943a89f5329abcff8b89d153eb500", + "sha256:fb7199b8fb0c50a22e77260bb59017e0c075fa80cb03bb2c8692de76e7bb7fe7", + "sha256:fbf7ecd31c39728f251b1c095fd27c84e4d21f60a1d079a0333472ff3ae59d34" + ], + "version": "==0.1.2" + }, + "idna": { + "hashes": [ + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + ], + "markers": "python_version >= '3.5'", + "version": "==3.2" + }, + "multidict": { + "hashes": [ + "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a", + "sha256:051012ccee979b2b06be928a6150d237aec75dd6bf2d1eeeb190baf2b05abc93", + "sha256:05c20b68e512166fddba59a918773ba002fdd77800cad9f55b59790030bab632", + "sha256:07b42215124aedecc6083f1ce6b7e5ec5b50047afa701f3442054373a6deb656", + "sha256:0e3c84e6c67eba89c2dbcee08504ba8644ab4284863452450520dad8f1e89b79", + "sha256:0e929169f9c090dae0646a011c8b058e5e5fb391466016b39d21745b48817fd7", + "sha256:1ab820665e67373de5802acae069a6a05567ae234ddb129f31d290fc3d1aa56d", + "sha256:25b4e5f22d3a37ddf3effc0710ba692cfc792c2b9edfb9c05aefe823256e84d5", + "sha256:2e68965192c4ea61fff1b81c14ff712fc7dc15d2bd120602e4a3494ea6584224", + "sha256:2f1a132f1c88724674271d636e6b7351477c27722f2ed789f719f9e3545a3d26", + "sha256:37e5438e1c78931df5d3c0c78ae049092877e5e9c02dd1ff5abb9cf27a5914ea", + "sha256:3a041b76d13706b7fff23b9fc83117c7b8fe8d5fe9e6be45eee72b9baa75f348", + "sha256:3a4f32116f8f72ecf2a29dabfb27b23ab7cdc0ba807e8459e59a93a9be9506f6", + "sha256:46c73e09ad374a6d876c599f2328161bcd95e280f84d2060cf57991dec5cfe76", + "sha256:46dd362c2f045095c920162e9307de5ffd0a1bfbba0a6e990b344366f55a30c1", + "sha256:4b186eb7d6ae7c06eb4392411189469e6a820da81447f46c0072a41c748ab73f", + "sha256:54fd1e83a184e19c598d5e70ba508196fd0bbdd676ce159feb412a4a6664f952", + "sha256:585fd452dd7782130d112f7ddf3473ffdd521414674c33876187e101b588738a", + "sha256:5cf3443199b83ed9e955f511b5b241fd3ae004e3cb81c58ec10f4fe47c7dce37", + "sha256:6a4d5ce640e37b0efcc8441caeea8f43a06addace2335bd11151bc02d2ee31f9", + "sha256:7df80d07818b385f3129180369079bd6934cf70469f99daaebfac89dca288359", + "sha256:806068d4f86cb06af37cd65821554f98240a19ce646d3cd24e1c33587f313eb8", + "sha256:830f57206cc96ed0ccf68304141fec9481a096c4d2e2831f311bde1c404401da", + "sha256:929006d3c2d923788ba153ad0de8ed2e5ed39fdbe8e7be21e2f22ed06c6783d3", + "sha256:9436dc58c123f07b230383083855593550c4d301d2532045a17ccf6eca505f6d", + "sha256:9dd6e9b1a913d096ac95d0399bd737e00f2af1e1594a787e00f7975778c8b2bf", + "sha256:ace010325c787c378afd7f7c1ac66b26313b3344628652eacd149bdd23c68841", + "sha256:b47a43177a5e65b771b80db71e7be76c0ba23cc8aa73eeeb089ed5219cdbe27d", + "sha256:b797515be8743b771aa868f83563f789bbd4b236659ba52243b735d80b29ed93", + "sha256:b7993704f1a4b204e71debe6095150d43b2ee6150fa4f44d6d966ec356a8d61f", + "sha256:d5c65bdf4484872c4af3150aeebe101ba560dcfb34488d9a8ff8dbcd21079647", + "sha256:d81eddcb12d608cc08081fa88d046c78afb1bf8107e6feab5d43503fea74a635", + "sha256:dc862056f76443a0db4509116c5cd480fe1b6a2d45512a653f9a855cc0517456", + "sha256:ecc771ab628ea281517e24fd2c52e8f31c41e66652d07599ad8818abaad38cda", + "sha256:f200755768dc19c6f4e2b672421e0ebb3dd54c38d5a4f262b872d8cfcc9e93b5", + "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281", + "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80" + ], + "markers": "python_version >= '3.6'", + "version": "==5.1.0" + }, + "pydantic": { + "extras": [ + "email" + ], + "hashes": [ + "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd", + "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739", + "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f", + "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840", + "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23", + "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287", + "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62", + "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b", + "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb", + "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820", + "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3", + "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b", + "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e", + "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3", + "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316", + "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b", + "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4", + "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20", + "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e", + "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505", + "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1", + "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833" + ], + "index": "pypi", + "version": "==1.8.2" + }, + "python-dotenv": { + "hashes": [ + "sha256:dd8fe852847f4fbfadabf6183ddd4c824a9651f02d51714fa075c95561959c7d", + "sha256:effaac3c1e58d89b3ccb4d04a40dc7ad6e0275fda25fd75ae9d323e2465e202d" + ], + "version": "==0.18.0" + }, + "python-multipart": { + "hashes": [ + "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43" + ], + "version": "==0.0.5" + }, + "pyyaml": { + "hashes": [ + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "version": "==5.4.1" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "starlette": { + "hashes": [ + "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed", + "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa" + ], + "markers": "python_version >= '3.6'", + "version": "==0.14.2" + }, + "typing-extensions": { + "hashes": [ + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + ], + "version": "==3.10.0.0" + }, + "uvicorn": { + "extras": [ + "standard" + ], + "hashes": [ + "sha256:3292251b3c7978e8e4a7868f4baf7f7f7bb7e40c759ecc125c37e99cdea34202", + "sha256:7587f7b08bd1efd2b9bad809a3d333e972f1d11af8a5e52a9371ee3a5de71524" + ], + "index": "pypi", + "version": "==0.13.4" + }, + "uvloop": { + "hashes": [ + "sha256:114543c84e95df1b4ff546e6e3a27521580466a30127f12172a3278172ad68bc", + "sha256:19fa1d56c91341318ac5d417e7b61c56e9a41183946cc70c411341173de02c69", + "sha256:2bb0624a8a70834e54dde8feed62ed63b50bad7a1265c40d6403a2ac447bce01", + "sha256:42eda9f525a208fbc4f7cecd00fa15c57cc57646c76632b3ba2fe005004f051d", + "sha256:44cac8575bf168601424302045234d74e3561fbdbac39b2b54cc1d1d00b70760", + "sha256:6de130d0cb78985a5d080e323b86c5ecaf3af82f4890492c05981707852f983c", + "sha256:7ae39b11a5f4cec1432d706c21ecc62f9e04d116883178b09671aa29c46f7a47", + "sha256:90e56f17755e41b425ad19a08c41dc358fa7bf1226c0f8e54d4d02d556f7af7c", + "sha256:b45218c99795803fb8bdbc9435ff7f54e3a591b44cd4c121b02fa83affb61c7c", + "sha256:e5e5f855c9bf483ee6cd1eb9a179b740de80cb0ae2988e3fa22309b78e2ea0e7" + ], + "version": "==0.15.2" + }, + "watchgod": { + "hashes": [ + "sha256:48140d62b0ebe9dd9cf8381337f06351e1f2e70b2203fa9c6eff4e572ca84f29", + "sha256:d6c1ea21df37847ac0537ca0d6c2f4cdf513562e95f77bb93abbcf05573407b7" + ], + "version": "==0.7" + }, + "websockets": { + "hashes": [ + "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5", + "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5", + "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308", + "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb", + "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a", + "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c", + "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170", + "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422", + "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8", + "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485", + "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f", + "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8", + "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc", + "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779", + "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989", + "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1", + "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092", + "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824", + "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d", + "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55", + "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36", + "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b" + ], + "version": "==8.1" + }, + "yarl": { + "hashes": [ + "sha256:00d7ad91b6583602eb9c1d085a2cf281ada267e9a197e8b7cae487dadbfa293e", + "sha256:0355a701b3998dcd832d0dc47cc5dedf3874f966ac7f870e0f3a6788d802d434", + "sha256:15263c3b0b47968c1d90daa89f21fcc889bb4b1aac5555580d74565de6836366", + "sha256:2ce4c621d21326a4a5500c25031e102af589edb50c09b321049e388b3934eec3", + "sha256:31ede6e8c4329fb81c86706ba8f6bf661a924b53ba191b27aa5fcee5714d18ec", + "sha256:324ba3d3c6fee56e2e0b0d09bf5c73824b9f08234339d2b788af65e60040c959", + "sha256:329412812ecfc94a57cd37c9d547579510a9e83c516bc069470db5f75684629e", + "sha256:4736eaee5626db8d9cda9eb5282028cc834e2aeb194e0d8b50217d707e98bb5c", + "sha256:4953fb0b4fdb7e08b2f3b3be80a00d28c5c8a2056bb066169de00e6501b986b6", + "sha256:4c5bcfc3ed226bf6419f7a33982fb4b8ec2e45785a0561eb99274ebbf09fdd6a", + "sha256:547f7665ad50fa8563150ed079f8e805e63dd85def6674c97efd78eed6c224a6", + "sha256:5b883e458058f8d6099e4420f0cc2567989032b5f34b271c0827de9f1079a424", + "sha256:63f90b20ca654b3ecc7a8d62c03ffa46999595f0167d6450fa8383bab252987e", + "sha256:68dc568889b1c13f1e4745c96b931cc94fdd0defe92a72c2b8ce01091b22e35f", + "sha256:69ee97c71fee1f63d04c945f56d5d726483c4762845400a6795a3b75d56b6c50", + "sha256:6d6283d8e0631b617edf0fd726353cb76630b83a089a40933043894e7f6721e2", + "sha256:72a660bdd24497e3e84f5519e57a9ee9220b6f3ac4d45056961bf22838ce20cc", + "sha256:73494d5b71099ae8cb8754f1df131c11d433b387efab7b51849e7e1e851f07a4", + "sha256:7356644cbed76119d0b6bd32ffba704d30d747e0c217109d7979a7bc36c4d970", + "sha256:8a9066529240171b68893d60dca86a763eae2139dd42f42106b03cf4b426bf10", + "sha256:8aa3decd5e0e852dc68335abf5478a518b41bf2ab2f330fe44916399efedfae0", + "sha256:97b5bdc450d63c3ba30a127d018b866ea94e65655efaf889ebeabc20f7d12406", + "sha256:9ede61b0854e267fd565e7527e2f2eb3ef8858b301319be0604177690e1a3896", + "sha256:b2e9a456c121e26d13c29251f8267541bd75e6a1ccf9e859179701c36a078643", + "sha256:b5dfc9a40c198334f4f3f55880ecf910adebdcb2a0b9a9c23c9345faa9185721", + "sha256:bafb450deef6861815ed579c7a6113a879a6ef58aed4c3a4be54400ae8871478", + "sha256:c49ff66d479d38ab863c50f7bb27dee97c6627c5fe60697de15529da9c3de724", + "sha256:ce3beb46a72d9f2190f9e1027886bfc513702d748047b548b05dab7dfb584d2e", + "sha256:d26608cf178efb8faa5ff0f2d2e77c208f471c5a3709e577a7b3fd0445703ac8", + "sha256:d597767fcd2c3dc49d6eea360c458b65643d1e4dbed91361cf5e36e53c1f8c96", + "sha256:d5c32c82990e4ac4d8150fd7652b972216b204de4e83a122546dce571c1bdf25", + "sha256:d8d07d102f17b68966e2de0e07bfd6e139c7c02ef06d3a0f8d2f0f055e13bb76", + "sha256:e46fba844f4895b36f4c398c5af062a9808d1f26b2999c58909517384d5deda2", + "sha256:e6b5460dc5ad42ad2b36cca524491dfcaffbfd9c8df50508bddc354e787b8dc2", + "sha256:f040bcc6725c821a4c0665f3aa96a4d0805a7aaf2caf266d256b8ed71b9f041c", + "sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a", + "sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71" + ], + "markers": "python_version >= '3.6'", + "version": "==1.6.3" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "attrs": { + "hashes": [ + "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", + "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.2.0" + }, + "bandit": { + "hashes": [ + "sha256:216be4d044209fa06cf2a3e51b319769a51be8318140659719aa7a115c35ed07", + "sha256:8a4c7415254d75df8ff3c3b15cfe9042ecee628a1e40b44c15a98890fbfc2608" + ], + "index": "pypi", + "version": "==1.7.0" + }, + "black": { + "hashes": [ + "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04", + "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7" + ], + "index": "pypi", + "version": "==21.6b0" + }, + "certifi": { + "hashes": [ + "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", + "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" + ], + "version": "==2021.5.30" + }, + "chardet": { + "hashes": [ + "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", + "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==4.0.0" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==7.1.2" + }, + "coverage": { + "hashes": [ + "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c", + "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6", + "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45", + "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a", + "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03", + "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529", + "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a", + "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a", + "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2", + "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6", + "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759", + "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53", + "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a", + "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4", + "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff", + "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502", + "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793", + "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb", + "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905", + "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821", + "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b", + "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81", + "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0", + "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b", + "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3", + "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184", + "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701", + "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a", + "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82", + "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638", + "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5", + "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083", + "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6", + "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90", + "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465", + "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a", + "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3", + "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e", + "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066", + "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf", + "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b", + "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae", + "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669", + "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873", + "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b", + "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6", + "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb", + "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160", + "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c", + "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079", + "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d", + "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==5.5" + }, + "diff-cover": { + "hashes": [ + "sha256:1953185a930f6c973d7ea752989246e7bf6d3181b73e925437664a47e39c63ff", + "sha256:e80f7a4979b7a9d373903776588279ed2898f17729e608255c4119462c822edb" + ], + "index": "pypi", + "version": "==5.0.1" + }, + "dparse": { + "hashes": [ + "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367", + "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994" + ], + "markers": "python_version >= '3.5'", + "version": "==0.5.1" + }, + "faker": { + "hashes": [ + "sha256:26c7c3df8d46f1db595a34962f8967021dd90bbd38cc6e27461a3fb16cd413ae", + "sha256:44eb060fad3015690ff3fec6564d7171be393021e820ad1851d96cb968fbfcd4" + ], + "index": "pypi", + "version": "==8.1.0" + }, + "flake8": { + "hashes": [ + "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b", + "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907" + ], + "index": "pypi", + "version": "==3.9.2" + }, + "flake8-print": { + "hashes": [ + "sha256:5afac374b7dc49aac2c36d04b5eb1d746d72e6f5df75a6ecaecd99e9f79c6516", + "sha256:6c0efce658513169f96d7a24cf136c434dc711eb00ebd0a985eb1120103fe584" + ], + "index": "pypi", + "version": "==4.0.0" + }, + "freezegun": { + "hashes": [ + "sha256:177f9dd59861d871e27a484c3332f35a6e3f5d14626f2bf91be37891f18927f3", + "sha256:2ae695f7eb96c62529f03a038461afe3c692db3465e215355e1bb4b0ab408712" + ], + "index": "pypi", + "version": "==1.1.0" + }, + "gitdb": { + "hashes": [ + "sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0", + "sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005" + ], + "markers": "python_version >= '3.4'", + "version": "==4.0.7" + }, + "gitpython": { + "hashes": [ + "sha256:b838a895977b45ab6f0cc926a9045c8d1c44e2b653c1fcc39fe91f42c6e8f05b", + "sha256:fce760879cd2aebd2991b3542876dc5c4a909b30c9d69dfc488e504a8db37ee8" + ], + "markers": "python_version >= '3.6'", + "version": "==3.1.18" + }, + "idna": { + "hashes": [ + "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a", + "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" + ], + "markers": "python_version >= '3.5'", + "version": "==3.2" + }, + "inflect": { + "hashes": [ + "sha256:41a23f6788962e9775e40e2ecfb1d6455d02de315022afeedd3c5dc070019d73", + "sha256:42560be16af702a21d43d59427f276b5aed79efb1ded9b713468c081f4353d10" + ], + "markers": "python_version >= '3.6'", + "version": "==5.3.0" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "jinja2": { + "hashes": [ + "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", + "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" + ], + "markers": "python_version >= '3.6'", + "version": "==3.0.1" + }, + "jinja2-pluralize": { + "hashes": [ + "sha256:4fec874a591014774d4c66cb7f65314390731bfc57db4c27119db61aa93b2bc4", + "sha256:df5c2d5017b9b54c0a66cb790cca9fc08945837c3dbfc323589203f1ffb73c1c" + ], + "version": "==0.3.0" + }, + "markupsafe": { + "hashes": [ + "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", + "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", + "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", + "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", + "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", + "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", + "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", + "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", + "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", + "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", + "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", + "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", + "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", + "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", + "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", + "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", + "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", + "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", + "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", + "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", + "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", + "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", + "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", + "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", + "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", + "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", + "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", + "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", + "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", + "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", + "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", + "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", + "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", + "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" + ], + "markers": "python_version >= '3.6'", + "version": "==2.0.1" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "mypy": { + "hashes": [ + "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9", + "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a", + "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9", + "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e", + "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2", + "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212", + "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b", + "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885", + "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150", + "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703", + "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072", + "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457", + "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e", + "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0", + "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb", + "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97", + "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8", + "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811", + "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6", + "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de", + "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504", + "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921", + "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d" + ], + "index": "pypi", + "version": "==0.910" + }, + "mypy-extensions": { + "hashes": [ + "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", + "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" + ], + "version": "==0.4.3" + }, + "packaging": { + "hashes": [ + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" + ], + "markers": "python_version >= '3.6'", + "version": "==21.0" + }, + "pathspec": { + "hashes": [ + "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd", + "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d" + ], + "version": "==0.8.1" + }, + "pbr": { + "hashes": [ + "sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd", + "sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4" + ], + "markers": "python_version >= '2.6'", + "version": "==5.6.0" + }, + "pluggy": { + "hashes": [ + "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", + "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.13.1" + }, + "py": { + "hashes": [ + "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", + "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.10.0" + }, + "pycodestyle": { + "hashes": [ + "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", + "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.7.0" + }, + "pyflakes": { + "hashes": [ + "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", + "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.3.1" + }, + "pygments": { + "hashes": [ + "sha256:a18f47b506a429f6f4b9df81bb02beab9ca21d0a5fee38ed15aef65f0545519f", + "sha256:d66e804411278594d764fc69ec36ec13d9ae9147193a1740cd34d272ca383b8e" + ], + "markers": "python_version >= '3.5'", + "version": "==2.9.0" + }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, + "pytest": { + "hashes": [ + "sha256:50bcad0a0b9c5a72c8e4e7c9855a3ad496ca6a881a3641b4260605450772c54b", + "sha256:91ef2131a9bd6be8f76f1f08eac5c5317221d6ad1e143ae03894b862e8976890" + ], + "index": "pypi", + "version": "==6.2.4" + }, + "pytest-asyncio": { + "hashes": [ + "sha256:5d9f9f64ab5ed41dc90e4d18dcbf0a74cdaac3d3dc30eb1cc1e49246dc382f3c", + "sha256:eeff4bd913405770882427d62685a8973681932f2ef53e964371af339628ad8a" + ], + "index": "pypi", + "version": "==0.15.0" + }, + "pytest-cov": { + "hashes": [ + "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a", + "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7" + ], + "index": "pypi", + "version": "==2.12.1" + }, + "python-dateutil": { + "hashes": [ + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.8.1" + }, + "pyyaml": { + "hashes": [ + "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", + "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696", + "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393", + "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77", + "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922", + "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5", + "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8", + "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10", + "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc", + "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018", + "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e", + "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253", + "sha256:72a01f726a9c7851ca9bfad6fd09ca4e090a023c00945ea05ba1638c09dc3347", + "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183", + "sha256:895f61ef02e8fed38159bb70f7e100e00f471eae2bc838cd0f4ebb21e28f8541", + "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb", + "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185", + "sha256:bfb51918d4ff3d77c1c856a9699f8492c612cde32fd3bcd344af9be34999bfdc", + "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db", + "sha256:cb333c16912324fd5f769fff6bc5de372e9e7a202247b48870bc251ed40239aa", + "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46", + "sha256:d483ad4e639292c90170eb6f7783ad19490e7a8defb3e46f97dfe4bacae89122", + "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b", + "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63", + "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df", + "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc", + "sha256:fd7f6999a8070df521b6384004ef42833b9bd62cfee11a09bda1079b4b704247", + "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6", + "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0" + ], + "version": "==5.4.1" + }, + "regex": { + "hashes": [ + "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f", + "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad", + "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a", + "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf", + "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59", + "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d", + "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895", + "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4", + "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3", + "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222", + "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0", + "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c", + "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417", + "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d", + "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d", + "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761", + "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0", + "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026", + "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854", + "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb", + "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d", + "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068", + "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde", + "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d", + "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec", + "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa", + "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd", + "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b", + "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26", + "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2", + "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f", + "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694", + "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0", + "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407", + "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874", + "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035", + "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d", + "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c", + "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5", + "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985", + "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58" + ], + "version": "==2021.7.6" + }, + "requests": { + "hashes": [ + "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", + "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==2.25.1" + }, + "safety": { + "hashes": [ + "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5", + "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84" + ], + "index": "pypi", + "version": "==1.10.3" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, + "smmap": { + "hashes": [ + "sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182", + "sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2" + ], + "markers": "python_version >= '3.5'", + "version": "==4.0.0" + }, + "stevedore": { + "hashes": [ + "sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee", + "sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a" + ], + "markers": "python_version >= '3.6'", + "version": "==3.3.0" + }, + "text-unidecode": { + "hashes": [ + "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", + "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" + ], + "version": "==1.3" + }, + "toml": { + "hashes": [ + "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", + "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.10.2" + }, + "types-requests": { + "hashes": [ + "sha256:ee0d0c507210141b7d5b8639cc43eaa726084178775db2a5fb06fbf85c185808", + "sha256:fa5c1e5e832ff6193507d8da7e1159281383908ee193a2f4b37bc08140b51844" + ], + "index": "pypi", + "version": "==2.25.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", + "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", + "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" + ], + "version": "==3.10.0.0" + }, + "urllib3": { + "hashes": [ + "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4", + "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "version": "==1.26.6" + }, + "vulture": { + "hashes": [ + "sha256:03d5a62bcbe9ceb9a9b0575f42d71a2d414070229f2e6f95fa6e7c71aaaed967", + "sha256:f39de5e6f1df1f70c3b50da54f1c8d494159e9ca3d01a9b89eac929600591703" + ], + "index": "pypi", + "version": "==2.3" + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb8f24d --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# GraphQL File Upload + +Short description about the package. + +- 📀 [Development server](http://127.0.0.1:8000/graphql/) + +## 🚀 Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. + +### 💼 Prerequisites + +You need: + +- `Python 3` installed on your local machine. + - use `pyenv` to manage multiple versions (**recommended**): [link](https://github.com/pyenv/pyenv#simple-python-version-management-pyenv), + - or you will find installation steps for your platform on the + python [official website](https://www.python.org/downloads/), +- `pipenv` for virtual environment management: [link](https://formulae.brew.sh/formula/pipenv). + +### 🖥 Installing + +Clone the project: + +```shell script +git clone https://github.com/rjNemo/graphql-file_upload.git +``` + +Create a virtual environment and install dependencies with: + +```shell script +make local-setup +``` + +Then launch the development server using: + +```shell script +make run +``` + +A GraphiQL API explorer is available on the development server address: http://127.0.0.1:8000/graphql/. + +🎉 Enjoy! + +### ⚡️ Scripts + +See [Makefile](Makefile) for available scripts: + +- dev server +- run tests + +## 🧪 Running the tests + +Tests are run using `pytest` and the test coverage is checked using `pytest-cov`. + +```shell script +make test +``` + +### Unit tests + +```shell script +pipenv run test +``` + +### Functional tests + +```shell script +pipenv run python -m pytest tests/functional_api +``` + +### 💅 And coding style tests + +Coding style is enforced using `black` for formatting, `flake8` for linting and `mypy` for static type checking. Additional +security check are performed using `bandit` and `safety`. + +```shell script +make lint +``` + +❗️ It is encouraged to run linters and tests using `make lint` before committing to the repository. + +## 👩‍🏫 GraphQL: GraphCool-Grammar + +We use a GraphQL grammar to structure the endpoints systematically. + +![GraphCool Grammar](./docs/graphcool_grammer.png) + +## 🛠 Built with + +- [FastAPI](https://fastapi.tiangolo.com/) - FastAPI framework, high performance, easy to learn, fast to code, ready for production +- [Ariadne](https://ariadnegraphql.org/) - Python GraphQL schema-first + +## 💻 Contributing + +Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting +merge requests. diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__main__.py b/app/__main__.py new file mode 100644 index 0000000..d84a517 --- /dev/null +++ b/app/__main__.py @@ -0,0 +1,5 @@ +import uvicorn + +from .main import app + +uvicorn.run(app, host="127.0.0.1", port=8000) diff --git a/app/core/__init__.py b/app/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/core/app.py b/app/core/app.py new file mode 100644 index 0000000..4184ea7 --- /dev/null +++ b/app/core/app.py @@ -0,0 +1,19 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from .config import ALLOWED_HOSTS, DEBUG, PROJECT_NAME, VERSION +from .graphql import mount_graphql + + +def get_application() -> FastAPI: + app = FastAPI(title=PROJECT_NAME, debug=DEBUG, version=VERSION) + # middleware are executed in reverse order (it's an onion) + app.add_middleware( + CORSMiddleware, + allow_origins=ALLOWED_HOSTS or ["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) + + return mount_graphql(app) diff --git a/app/core/config.py b/app/core/config.py new file mode 100644 index 0000000..f1338c3 --- /dev/null +++ b/app/core/config.py @@ -0,0 +1,23 @@ +import os +from pathlib import Path +from typing import List + +from starlette.config import Config +from starlette.datastructures import CommaSeparatedStrings + +config = Config(".env") + +# ------------------------------------------------------------------------------------------------------------ +# Application +# ------------------------------------------------------------------------------------------------------------ +VERSION = "0.1.0" + +DEBUG: bool = config("DEBUG", cast=bool, default=os.getenv("DEBUG", False)) + +BASE_DIR = config("BASE_DIR", default=os.getenv("BASE_DIR", Path(__file__).parent.parent.parent)) + +PROJECT_NAME = config("PROJECT_NAME", default=os.getenv("PROJECT_NAME", "domain-service")) + +ALLOWED_HOSTS: List[str] = config( + "ALLOWED_HOSTS", cast=CommaSeparatedStrings, default=os.getenv("ALLOWED_HOSTS") +) diff --git a/app/core/graphql.py b/app/core/graphql.py new file mode 100644 index 0000000..f1a95b2 --- /dev/null +++ b/app/core/graphql.py @@ -0,0 +1,20 @@ +from ariadne import make_executable_schema, snake_case_fallback_resolvers, upload_scalar +from ariadne.asgi import GraphQL +from fastapi import FastAPI + +from ..schema import Mutation, Query, schema +from .config import DEBUG + + +def mount_graphql(app: FastAPI, path: str = "/graphql") -> FastAPI: + app.mount( + path, + GraphQL( + make_executable_schema( + schema, Query, Mutation, snake_case_fallback_resolvers, upload_scalar + ), + debug=DEBUG, + ), + ) + + return app diff --git a/app/database/__init__.py b/app/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/database/file.txt b/app/database/file.txt new file mode 100644 index 0000000..b43bf86 --- /dev/null +++ b/app/database/file.txt @@ -0,0 +1 @@ +README.md diff --git a/app/file/__init__.py b/app/file/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/file/mutations/__init__.py b/app/file/mutations/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/app/file/mutations/__init__.py @@ -0,0 +1 @@ + diff --git a/app/file/mutations/upload.py b/app/file/mutations/upload.py new file mode 100644 index 0000000..497b0ce --- /dev/null +++ b/app/file/mutations/upload.py @@ -0,0 +1,20 @@ +import os +from typing import Any + +from ariadne import convert_kwargs_to_snake_case +from fastapi import File, UploadFile +from graphql import GraphQLResolveInfo + +from ...core.config import BASE_DIR + + +@convert_kwargs_to_snake_case +async def mutate_upload_user_image( + _: Any, _info: GraphQLResolveInfo, image: UploadFile = File(...) +) -> bool: + with open(os.path.join(BASE_DIR, "app", "database", "file.txt"), "a") as f: + f.write(f"{image.filename}\n") + with open(os.path.join(str(os.getcwd()), "app", "database", "file.txt"), "r") as f: + print(f.read()) + + return True diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..23a9764 --- /dev/null +++ b/app/main.py @@ -0,0 +1,3 @@ +from .core.app import get_application + +app = get_application() diff --git a/app/schema/__init__.py b/app/schema/__init__.py new file mode 100644 index 0000000..b6d8bc4 --- /dev/null +++ b/app/schema/__init__.py @@ -0,0 +1,3 @@ +from .mutation import Mutation +from .query import Query +from .schema import schema diff --git a/app/schema/mutation.graphql b/app/schema/mutation.graphql new file mode 100644 index 0000000..2e805dc --- /dev/null +++ b/app/schema/mutation.graphql @@ -0,0 +1,6 @@ +type Mutation { + """ + Upload user generated image + """ + uploadUserImage(image: Upload!): Boolean! +} diff --git a/app/schema/mutation.py b/app/schema/mutation.py new file mode 100644 index 0000000..d1452e5 --- /dev/null +++ b/app/schema/mutation.py @@ -0,0 +1,6 @@ +from ariadne import MutationType + +from app.file.mutations.upload import mutate_upload_user_image + +Mutation = MutationType() +Mutation.set_field("uploadUserImage", mutate_upload_user_image) diff --git a/app/schema/query.graphql b/app/schema/query.graphql new file mode 100644 index 0000000..0061a32 --- /dev/null +++ b/app/schema/query.graphql @@ -0,0 +1,3 @@ +type Query { + hello: String! +} diff --git a/app/schema/query.py b/app/schema/query.py new file mode 100644 index 0000000..dc788cc --- /dev/null +++ b/app/schema/query.py @@ -0,0 +1,10 @@ +from typing import Any + +from ariadne import QueryType + +Query = QueryType() + + +@Query.field("hello") +async def resolve_hello(*_: Any) -> str: + return "Hello stranger" diff --git a/app/schema/schema.py b/app/schema/schema.py new file mode 100644 index 0000000..4f94c21 --- /dev/null +++ b/app/schema/schema.py @@ -0,0 +1,3 @@ +from ariadne import load_schema_from_path + +schema = load_schema_from_path("./") diff --git a/app/schema/tests/__init__.py b/app/schema/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/schema/tests/test_graphql_schema.py b/app/schema/tests/test_graphql_schema.py new file mode 100644 index 0000000..548e461 --- /dev/null +++ b/app/schema/tests/test_graphql_schema.py @@ -0,0 +1,12 @@ +import pytest +from ariadne import gql +from graphql import GraphQLSyntaxError + +from ..schema import schema + + +def test_graphql_schema(): + try: + gql(schema) + except GraphQLSyntaxError: + pytest.fail("GraphQL schema are invalid") diff --git a/app/schema/upload.graphql b/app/schema/upload.graphql new file mode 100644 index 0000000..cf39658 --- /dev/null +++ b/app/schema/upload.graphql @@ -0,0 +1 @@ +scalar Upload diff --git a/docs/graphcool_grammer.png b/docs/graphcool_grammer.png new file mode 100644 index 0000000000000000000000000000000000000000..8aed4a2d48a1a9d4836caf3481097e3c624c24d8 GIT binary patch literal 53568 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 U#-`H*Tm$~e%cw{fO2Xd$KM>D;vj6}9 literal 0 HcmV?d00001 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..9130fd3 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,18 @@ +[flake8] +exclude = .git, __pycache__, __init__.py, app/migrations/* +max-complexity = 13 +max-line-length = 100 + +[mypy] +ignore_missing_imports = True +warn_unused_configs = True +no_implicit_optional = True +warn_unused_ignores = True +warn_unreachable = True +warn_redundant_casts = True +disallow_untyped_defs=True + +plugins = pydantic.mypy +[pydantic-mypy] +init_forbid_extra = True +init_typed = True \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functional_api/__init__.py b/tests/functional_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functional_api/client.py b/tests/functional_api/client.py new file mode 100644 index 0000000..d747fa7 --- /dev/null +++ b/tests/functional_api/client.py @@ -0,0 +1,34 @@ +from typing import Any, Optional, Union + +from aiohttp import ClientResponse +from fastapi.testclient import TestClient +from requests import Response +from starlette.testclient import ASGI2App, ASGI3App + + +class Client(TestClient): + UPLOAD_USER_FILE = """ + mutation($file: Upload!) { + uploadUserImage(image: $file) + } + """ + + HELLO = """ + query { + hello + } + """ + + def __init__(self, app: Union[ASGI2App, ASGI3App]) -> None: + super().__init__(app) + + async def upload_user_file(self, variables: Optional[dict]) -> ClientResponse: + ... + + def hello(self) -> Response: + data = {"query": self.HELLO} + + return self._graphql(data) + + def _graphql(self, data: dict, files: Any = None) -> Response: + return self.post("/graphql/", json=data, files=files) diff --git a/tests/functional_api/conftest.py b/tests/functional_api/conftest.py new file mode 100644 index 0000000..12af003 --- /dev/null +++ b/tests/functional_api/conftest.py @@ -0,0 +1,16 @@ +import pytest +from app.main import app +from faker import Faker + +from .client import Client + + +@pytest.fixture(scope="session") +def client() -> Client: + return Client(app) + + +@pytest.fixture(scope="session") +def faker() -> Faker: + faker = Faker() + return faker diff --git a/tests/functional_api/test_upload_image.py b/tests/functional_api/test_upload_image.py new file mode 100644 index 0000000..0d742b6 --- /dev/null +++ b/tests/functional_api/test_upload_image.py @@ -0,0 +1,30 @@ +import os + +import aiohttp +import pytest +from aiogqlc import GraphQLClient +from app.core.config import BASE_DIR + +from .conftest import Client + + +@pytest.mark.asyncio +async def test_foo(client: Client) -> None: + async with aiohttp.ClientSession() as session: + filename = "README.md" + variables = {"file": open(filename, "rb")} + + gql = GraphQLClient("http://127.0.0.1:8000/graphql/", session=session) + + response = await gql.execute(client.UPLOAD_USER_FILE, variables=variables) + print(await response.json()) + + with open(os.path.join(BASE_DIR, "app", "database", "file.txt")) as f: + files = [f.readline() for _ in f] + assert filename in files[-1] + + +def test_hello(client: Client) -> None: + response = client.hello() + + assert "hello" in response.json().get("data")