├── .github └── workflows │ └── pythonapp.yml ├── .gitignore ├── LICENSE ├── README.md ├── poetry.lock ├── pyproject.toml ├── src └── example │ ├── __init__.py │ ├── cli.py │ ├── crud │ ├── __init__.py │ └── item.py │ ├── db │ ├── __init__.py │ ├── base.py │ └── session.py │ ├── main.py │ ├── models │ ├── __init__.py │ └── item.py │ ├── routers │ ├── __init__.py │ ├── item.py │ └── utils │ │ ├── __init__.py │ │ └── db.py │ ├── routes.py │ ├── schemas │ ├── __init__.py │ └── item.py │ └── settings.py └── tests ├── conftest.py ├── test_crud_item.py └── test_routers_item.py /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python application 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up Python 3.8 20 | uses: actions/setup-python@v1 21 | with: 22 | python-version: 3.8 23 | - name: Install dependencies 24 | run: | 25 | python -m pip install --upgrade pip 26 | python -m pip install poetry 27 | poetry install 28 | - name: Lint with flake8 29 | run: | 30 | # stop the build if there are Python syntax errors or undefined names 31 | poetry run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 32 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 33 | poetry run flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 34 | - name: Test with pytest 35 | run: | 36 | poetry run pytest -v 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff 7 | .idea/**/workspace.xml 8 | .idea/**/tasks.xml 9 | .idea/**/usage.statistics.xml 10 | .idea/**/dictionaries 11 | .idea/**/shelf 12 | 13 | # Generated files 14 | .idea/**/contentModel.xml 15 | 16 | # Sensitive or high-churn files 17 | .idea/**/dataSources/ 18 | .idea/**/dataSources.ids 19 | .idea/**/dataSources.local.xml 20 | .idea/**/sqlDataSources.xml 21 | .idea/**/dynamic.xml 22 | .idea/**/uiDesigner.xml 23 | .idea/**/dbnavigator.xml 24 | 25 | # Gradle 26 | .idea/**/gradle.xml 27 | .idea/**/libraries 28 | 29 | # Gradle and Maven with auto-import 30 | # When using Gradle or Maven with auto-import, you should exclude module files, 31 | # since they will be recreated, and may cause churn. Uncomment if using 32 | # auto-import. 33 | # .idea/artifacts 34 | # .idea/compiler.xml 35 | # .idea/jarRepositories.xml 36 | # .idea/modules.xml 37 | # .idea/*.iml 38 | # .idea/modules 39 | # *.iml 40 | # *.ipr 41 | 42 | # CMake 43 | cmake-build-*/ 44 | 45 | # Mongo Explorer plugin 46 | .idea/**/mongoSettings.xml 47 | 48 | # File-based project format 49 | *.iws 50 | 51 | # IntelliJ 52 | out/ 53 | 54 | # mpeltonen/sbt-idea plugin 55 | .idea_modules/ 56 | 57 | # JIRA plugin 58 | atlassian-ide-plugin.xml 59 | 60 | # Cursive Clojure plugin 61 | .idea/replstate.xml 62 | 63 | # Crashlytics plugin (for Android Studio and IntelliJ) 64 | com_crashlytics_export_strings.xml 65 | crashlytics.properties 66 | crashlytics-build.properties 67 | fabric.properties 68 | 69 | # Editor-based Rest Client 70 | .idea/httpRequests 71 | 72 | # Android studio 3.1+ serialized cache file 73 | .idea/caches/build_file_checksums.ser 74 | 75 | ### Python template 76 | # Byte-compiled / optimized / DLL files 77 | __pycache__/ 78 | *.py[cod] 79 | *$py.class 80 | 81 | # C extensions 82 | *.so 83 | 84 | # Distribution / packaging 85 | .Python 86 | build/ 87 | develop-eggs/ 88 | dist/ 89 | downloads/ 90 | eggs/ 91 | .eggs/ 92 | lib/ 93 | lib64/ 94 | parts/ 95 | sdist/ 96 | var/ 97 | wheels/ 98 | pip-wheel-metadata/ 99 | share/python-wheels/ 100 | *.egg-info/ 101 | .installed.cfg 102 | *.egg 103 | MANIFEST 104 | 105 | # PyInstaller 106 | # Usually these files are written by a python script from a template 107 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 108 | *.manifest 109 | *.spec 110 | 111 | # Installer logs 112 | pip-log.txt 113 | pip-delete-this-directory.txt 114 | 115 | # Unit test / coverage reports 116 | htmlcov/ 117 | .tox/ 118 | .nox/ 119 | .coverage 120 | .coverage.* 121 | .cache 122 | nosetests.xml 123 | coverage.xml 124 | *.cover 125 | *.py,cover 126 | .hypothesis/ 127 | .pytest_cache/ 128 | cover/ 129 | 130 | # Translations 131 | *.mo 132 | *.pot 133 | 134 | # Django stuff: 135 | *.log 136 | local_settings.py 137 | db.sqlite3 138 | db.sqlite3-journal 139 | 140 | # Flask stuff: 141 | instance/ 142 | .webassets-cache 143 | 144 | # Scrapy stuff: 145 | .scrapy 146 | 147 | # Sphinx documentation 148 | docs/_build/ 149 | 150 | # PyBuilder 151 | .pybuilder/ 152 | target/ 153 | 154 | # Jupyter Notebook 155 | .ipynb_checkpoints 156 | 157 | # IPython 158 | profile_default/ 159 | ipython_config.py 160 | 161 | # pyenv 162 | # For a library or package, you might want to ignore these files since the code is 163 | # intended to run in multiple environments; otherwise, check them in: 164 | # .python-version 165 | 166 | # pipenv 167 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 168 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 169 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 170 | # install all needed dependencies. 171 | #Pipfile.lock 172 | 173 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 174 | __pypackages__/ 175 | 176 | # Celery stuff 177 | celerybeat-schedule 178 | celerybeat.pid 179 | 180 | # SageMath parsed files 181 | *.sage.py 182 | 183 | # Environments 184 | .env 185 | .venv 186 | env/ 187 | venv/ 188 | ENV/ 189 | env.bak/ 190 | venv.bak/ 191 | 192 | # Spyder project settings 193 | .spyderproject 194 | .spyproject 195 | 196 | # Rope project settings 197 | .ropeproject 198 | 199 | # mkdocs documentation 200 | /site 201 | 202 | # mypy 203 | .mypy_cache/ 204 | .dmypy.json 205 | dmypy.json 206 | 207 | # Pyre type checker 208 | .pyre/ 209 | 210 | # pytype static type analyzer 211 | .pytype/ 212 | 213 | # Cython debug symbols 214 | cython_debug/ 215 | 216 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Tim Hughes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # example-fastapi-sqlachemy-pytest 2 | 3 | ![Python application](https://github.com/timhughes/example-fastapi-sqlachemy-pytest/workflows/Python%20application/badge.svg) 4 | 5 | This containes an example app with pytest `conftest.py` fixtures that so far for me work with FastAPI. 6 | 7 | 8 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "apipkg" 3 | version = "1.5" 4 | description = "apipkg: namespace control and lazy-import mechanism" 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 8 | 9 | [[package]] 10 | name = "appdirs" 11 | version = "1.4.3" 12 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 13 | category = "dev" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "astroid" 19 | version = "2.3.3" 20 | description = "An abstract syntax tree for Python with inference support." 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=3.5.*" 24 | 25 | [package.dependencies] 26 | lazy-object-proxy = ">=1.4.0,<1.5.0" 27 | six = ">=1.12,<2.0" 28 | typed-ast = {version = ">=1.4.0,<1.5", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} 29 | wrapt = ">=1.11.0,<1.12.0" 30 | 31 | [[package]] 32 | name = "atomicwrites" 33 | version = "1.3.0" 34 | description = "Atomic file writes." 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 38 | 39 | [[package]] 40 | name = "attrs" 41 | version = "19.3.0" 42 | description = "Classes Without Boilerplate" 43 | category = "dev" 44 | optional = false 45 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 46 | 47 | [package.extras] 48 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 49 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 50 | docs = ["sphinx", "zope.interface"] 51 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 52 | 53 | [[package]] 54 | name = "autoflake" 55 | version = "1.3.1" 56 | description = "Removes unused imports and unused variables" 57 | category = "dev" 58 | optional = false 59 | python-versions = "*" 60 | 61 | [package.dependencies] 62 | pyflakes = ">=1.1.0" 63 | 64 | [[package]] 65 | name = "bacon" 66 | version = "0.2.0" 67 | description = "Bacon Game Engine" 68 | category = "dev" 69 | optional = false 70 | python-versions = "*" 71 | 72 | [[package]] 73 | name = "bcrypt" 74 | version = "3.1.7" 75 | description = "Modern password hashing for your software and your servers" 76 | category = "main" 77 | optional = false 78 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 79 | 80 | [package.dependencies] 81 | cffi = ">=1.1" 82 | six = ">=1.4.1" 83 | 84 | [package.extras] 85 | tests = ["pytest (>=3.2.1,!=3.3.0)"] 86 | 87 | [[package]] 88 | name = "black" 89 | version = "19.10b0" 90 | description = "The uncompromising code formatter." 91 | category = "dev" 92 | optional = false 93 | python-versions = ">=3.6" 94 | 95 | [package.dependencies] 96 | appdirs = "*" 97 | attrs = ">=18.1.0" 98 | click = ">=6.5" 99 | pathspec = ">=0.6,<1" 100 | regex = "*" 101 | toml = ">=0.9.4" 102 | typed-ast = ">=1.4.0" 103 | 104 | [package.extras] 105 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 106 | 107 | [[package]] 108 | name = "certifi" 109 | version = "2020.4.5.1" 110 | description = "Python package for providing Mozilla's CA Bundle." 111 | category = "dev" 112 | optional = false 113 | python-versions = "*" 114 | 115 | [[package]] 116 | name = "cffi" 117 | version = "1.14.0" 118 | description = "Foreign Function Interface for Python calling C code." 119 | category = "main" 120 | optional = false 121 | python-versions = "*" 122 | 123 | [package.dependencies] 124 | pycparser = "*" 125 | 126 | [[package]] 127 | name = "chardet" 128 | version = "3.0.4" 129 | description = "Universal encoding detector for Python 2 and 3" 130 | category = "dev" 131 | optional = false 132 | python-versions = "*" 133 | 134 | [[package]] 135 | name = "click" 136 | version = "7.1.1" 137 | description = "Composable command line interface toolkit" 138 | category = "main" 139 | optional = false 140 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 141 | 142 | [[package]] 143 | name = "colorama" 144 | version = "0.4.3" 145 | description = "Cross-platform colored terminal text." 146 | category = "dev" 147 | optional = false 148 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 149 | 150 | [[package]] 151 | name = "coverage" 152 | version = "5.1" 153 | description = "Code coverage measurement for Python" 154 | category = "dev" 155 | optional = false 156 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 157 | 158 | [package.extras] 159 | toml = ["toml"] 160 | 161 | [[package]] 162 | name = "distlib" 163 | version = "0.3.0" 164 | description = "Distribution utilities" 165 | category = "dev" 166 | optional = false 167 | python-versions = "*" 168 | 169 | [package.dependencies] 170 | bacon = "<=0.2" 171 | 172 | [[package]] 173 | name = "entrypoints" 174 | version = "0.3" 175 | description = "Discover and load entry points from installed packages." 176 | category = "dev" 177 | optional = false 178 | python-versions = ">=2.7" 179 | 180 | [[package]] 181 | name = "execnet" 182 | version = "1.7.1" 183 | description = "execnet: rapid multi-Python deployment" 184 | category = "dev" 185 | optional = false 186 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 187 | 188 | [package.dependencies] 189 | apipkg = ">=1.4" 190 | 191 | [package.extras] 192 | testing = ["pre-commit"] 193 | 194 | [[package]] 195 | name = "fastapi" 196 | version = "0.65.2" 197 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 198 | category = "main" 199 | optional = false 200 | python-versions = ">=3.6" 201 | 202 | [package.dependencies] 203 | pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0" 204 | starlette = "0.14.2" 205 | 206 | [package.extras] 207 | all = ["requests (>=2.24.0,<3.0.0)", "aiofiles (>=0.5.0,<0.6.0)", "jinja2 (>=2.11.2,<3.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "itsdangerous (>=1.1.0,<2.0.0)", "pyyaml (>=5.3.1,<6.0.0)", "graphene (>=2.1.8,<3.0.0)", "ujson (>=4.0.1,<5.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] 208 | dev = ["python-jose[cryptography] (>=3.1.0,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.14.0)", "graphene (>=2.1.8,<3.0.0)"] 209 | doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=6.1.4,<7.0.0)", "markdown-include (>=0.5.1,<0.6.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.2.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] 210 | test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.812)", "flake8 (>=3.8.3,<4.0.0)", "black (==20.8b1)", "isort (>=5.0.6,<6.0.0)", "requests (>=2.24.0,<3.0.0)", "httpx (>=0.14.0,<0.15.0)", "email_validator (>=1.1.1,<2.0.0)", "sqlalchemy (>=1.3.18,<1.4.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.0.0)", "ujson (>=4.0.1,<5.0.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "aiofiles (>=0.5.0,<0.6.0)", "flask (>=1.1.2,<2.0.0)"] 211 | 212 | [[package]] 213 | name = "filelock" 214 | version = "3.0.12" 215 | description = "A platform independent file lock." 216 | category = "dev" 217 | optional = false 218 | python-versions = "*" 219 | 220 | [[package]] 221 | name = "flake8" 222 | version = "3.7.9" 223 | description = "the modular source code checker: pep8, pyflakes and co" 224 | category = "dev" 225 | optional = false 226 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 227 | 228 | [package.dependencies] 229 | entrypoints = ">=0.3.0,<0.4.0" 230 | mccabe = ">=0.6.0,<0.7.0" 231 | pycodestyle = ">=2.5.0,<2.6.0" 232 | pyflakes = ">=2.1.0,<2.2.0" 233 | 234 | [[package]] 235 | name = "h11" 236 | version = "0.9.0" 237 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 238 | category = "main" 239 | optional = false 240 | python-versions = "*" 241 | 242 | [[package]] 243 | name = "httptools" 244 | version = "0.1.1" 245 | description = "A collection of framework independent HTTP protocol utils." 246 | category = "main" 247 | optional = false 248 | python-versions = "*" 249 | 250 | [package.extras] 251 | test = ["Cython (==0.29.14)"] 252 | 253 | [[package]] 254 | name = "idna" 255 | version = "2.9" 256 | description = "Internationalized Domain Names in Applications (IDNA)" 257 | category = "dev" 258 | optional = false 259 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 260 | 261 | [[package]] 262 | name = "importlib-metadata" 263 | version = "1.6.0" 264 | description = "Read metadata from Python packages" 265 | category = "dev" 266 | optional = false 267 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 268 | 269 | [package.dependencies] 270 | zipp = ">=0.5" 271 | 272 | [package.extras] 273 | docs = ["sphinx", "rst.linker"] 274 | testing = ["packaging", "importlib-resources"] 275 | 276 | [[package]] 277 | name = "isort" 278 | version = "4.3.21" 279 | description = "A Python utility / library to sort Python imports." 280 | category = "dev" 281 | optional = false 282 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 283 | 284 | [package.extras] 285 | pipfile = ["pipreqs", "requirementslib"] 286 | pyproject = ["toml"] 287 | requirements = ["pipreqs", "pip-api"] 288 | xdg_home = ["appdirs (>=1.4.0)"] 289 | 290 | [[package]] 291 | name = "lazy-object-proxy" 292 | version = "1.4.3" 293 | description = "A fast and thorough lazy object proxy." 294 | category = "dev" 295 | optional = false 296 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 297 | 298 | [[package]] 299 | name = "mccabe" 300 | version = "0.6.1" 301 | description = "McCabe checker, plugin for flake8" 302 | category = "dev" 303 | optional = false 304 | python-versions = "*" 305 | 306 | [[package]] 307 | name = "more-itertools" 308 | version = "8.2.0" 309 | description = "More routines for operating on iterables, beyond itertools" 310 | category = "dev" 311 | optional = false 312 | python-versions = ">=3.5" 313 | 314 | [[package]] 315 | name = "mypy" 316 | version = "0.770" 317 | description = "Optional static typing for Python" 318 | category = "dev" 319 | optional = false 320 | python-versions = ">=3.5" 321 | 322 | [package.dependencies] 323 | mypy-extensions = ">=0.4.3,<0.5.0" 324 | typed-ast = ">=1.4.0,<1.5.0" 325 | typing-extensions = ">=3.7.4" 326 | 327 | [package.extras] 328 | dmypy = ["psutil (>=4.0)"] 329 | 330 | [[package]] 331 | name = "mypy-extensions" 332 | version = "0.4.3" 333 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 334 | category = "dev" 335 | optional = false 336 | python-versions = "*" 337 | 338 | [[package]] 339 | name = "packaging" 340 | version = "20.3" 341 | description = "Core utilities for Python packages" 342 | category = "dev" 343 | optional = false 344 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 345 | 346 | [package.dependencies] 347 | pyparsing = ">=2.0.2" 348 | six = "*" 349 | 350 | [[package]] 351 | name = "passlib" 352 | version = "1.7.2" 353 | description = "comprehensive password hashing framework supporting over 30 schemes" 354 | category = "main" 355 | optional = false 356 | python-versions = "*" 357 | 358 | [package.extras] 359 | argon2 = ["argon2-cffi (>=18.2.0)"] 360 | bcrypt = ["bcrypt (>=3.1.0)"] 361 | build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.0)"] 362 | totp = ["cryptography"] 363 | 364 | [[package]] 365 | name = "pathspec" 366 | version = "0.8.0" 367 | description = "Utility library for gitignore style pattern matching of file paths." 368 | category = "dev" 369 | optional = false 370 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 371 | 372 | [[package]] 373 | name = "pluggy" 374 | version = "0.13.1" 375 | description = "plugin and hook calling mechanisms for python" 376 | category = "dev" 377 | optional = false 378 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 379 | 380 | [package.dependencies] 381 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 382 | 383 | [package.extras] 384 | dev = ["pre-commit", "tox"] 385 | 386 | [[package]] 387 | name = "py" 388 | version = "1.8.1" 389 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 390 | category = "dev" 391 | optional = false 392 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 393 | 394 | [[package]] 395 | name = "pycodestyle" 396 | version = "2.5.0" 397 | description = "Python style guide checker" 398 | category = "dev" 399 | optional = false 400 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 401 | 402 | [[package]] 403 | name = "pycparser" 404 | version = "2.20" 405 | description = "C parser in Python" 406 | category = "main" 407 | optional = false 408 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 409 | 410 | [[package]] 411 | name = "pydantic" 412 | version = "1.7.4" 413 | description = "Data validation and settings management using python 3.6 type hinting" 414 | category = "main" 415 | optional = false 416 | python-versions = ">=3.6" 417 | 418 | [package.extras] 419 | dotenv = ["python-dotenv (>=0.10.4)"] 420 | email = ["email-validator (>=1.0.3)"] 421 | typing_extensions = ["typing-extensions (>=3.7.2)"] 422 | 423 | [[package]] 424 | name = "pyflakes" 425 | version = "2.1.1" 426 | description = "passive checker of Python programs" 427 | category = "dev" 428 | optional = false 429 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 430 | 431 | [[package]] 432 | name = "pyjwt" 433 | version = "1.7.1" 434 | description = "JSON Web Token implementation in Python" 435 | category = "main" 436 | optional = false 437 | python-versions = "*" 438 | 439 | [package.extras] 440 | crypto = ["cryptography (>=1.4)"] 441 | flake8 = ["flake8", "flake8-import-order", "pep8-naming"] 442 | test = ["pytest (>=4.0.1,<5.0.0)", "pytest-cov (>=2.6.0,<3.0.0)", "pytest-runner (>=4.2,<5.0.0)"] 443 | 444 | [[package]] 445 | name = "pylint" 446 | version = "2.4.4" 447 | description = "python code static checker" 448 | category = "dev" 449 | optional = false 450 | python-versions = ">=3.5.*" 451 | 452 | [package.dependencies] 453 | astroid = ">=2.3.0,<2.4" 454 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 455 | isort = ">=4.2.5,<5" 456 | mccabe = ">=0.6,<0.7" 457 | 458 | [[package]] 459 | name = "pyparsing" 460 | version = "2.4.7" 461 | description = "Python parsing module" 462 | category = "dev" 463 | optional = false 464 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 465 | 466 | [[package]] 467 | name = "pytest" 468 | version = "5.4.1" 469 | description = "pytest: simple powerful testing with Python" 470 | category = "dev" 471 | optional = false 472 | python-versions = ">=3.5" 473 | 474 | [package.dependencies] 475 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 476 | attrs = ">=17.4.0" 477 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 478 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 479 | more-itertools = ">=4.0.0" 480 | packaging = "*" 481 | pluggy = ">=0.12,<1.0" 482 | py = ">=1.5.0" 483 | wcwidth = "*" 484 | 485 | [package.extras] 486 | checkqa-mypy = ["mypy (==v0.761)"] 487 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 488 | 489 | [[package]] 490 | name = "pytest-cov" 491 | version = "2.8.1" 492 | description = "Pytest plugin for measuring coverage." 493 | category = "dev" 494 | optional = false 495 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 496 | 497 | [package.dependencies] 498 | coverage = ">=4.4" 499 | pytest = ">=3.6" 500 | 501 | [package.extras] 502 | testing = ["fields", "hunter", "process-tests (==2.0.2)", "six", "virtualenv"] 503 | 504 | [[package]] 505 | name = "pytest-forked" 506 | version = "1.1.3" 507 | description = "run tests in isolated forked subprocesses" 508 | category = "dev" 509 | optional = false 510 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 511 | 512 | [package.dependencies] 513 | pytest = ">=3.1.0" 514 | 515 | [[package]] 516 | name = "pytest-xdist" 517 | version = "1.31.0" 518 | description = "pytest xdist plugin for distributed testing and loop-on-failing modes" 519 | category = "dev" 520 | optional = false 521 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 522 | 523 | [package.dependencies] 524 | execnet = ">=1.1" 525 | pytest = ">=4.4.0" 526 | pytest-forked = "*" 527 | six = "*" 528 | 529 | [package.extras] 530 | testing = ["filelock"] 531 | 532 | [[package]] 533 | name = "python-multipart" 534 | version = "0.0.5" 535 | description = "A streaming multipart parser for Python" 536 | category = "main" 537 | optional = false 538 | python-versions = "*" 539 | 540 | [package.dependencies] 541 | six = ">=1.4.0" 542 | 543 | [[package]] 544 | name = "regex" 545 | version = "2020.4.4" 546 | description = "Alternative regular expression module, to replace re." 547 | category = "dev" 548 | optional = false 549 | python-versions = "*" 550 | 551 | [[package]] 552 | name = "requests" 553 | version = "2.23.0" 554 | description = "Python HTTP for Humans." 555 | category = "dev" 556 | optional = false 557 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 558 | 559 | [package.dependencies] 560 | certifi = ">=2017.4.17" 561 | chardet = ">=3.0.2,<4" 562 | idna = ">=2.5,<3" 563 | urllib3 = ">=1.21.1,<1.25.0 || >1.25.0,<1.25.1 || >1.25.1,<1.26" 564 | 565 | [package.extras] 566 | security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"] 567 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 568 | 569 | [[package]] 570 | name = "six" 571 | version = "1.14.0" 572 | description = "Python 2 and 3 compatibility utilities" 573 | category = "main" 574 | optional = false 575 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 576 | 577 | [[package]] 578 | name = "sqlalchemy" 579 | version = "1.3.16" 580 | description = "Database Abstraction Library" 581 | category = "main" 582 | optional = false 583 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 584 | 585 | [package.extras] 586 | mssql = ["pyodbc"] 587 | mssql_pymssql = ["pymssql"] 588 | mssql_pyodbc = ["pyodbc"] 589 | mysql = ["mysqlclient"] 590 | oracle = ["cx-oracle"] 591 | postgresql = ["psycopg2"] 592 | postgresql_pg8000 = ["pg8000"] 593 | postgresql_psycopg2binary = ["psycopg2-binary"] 594 | postgresql_psycopg2cffi = ["psycopg2cffi"] 595 | pymysql = ["pymysql"] 596 | 597 | [[package]] 598 | name = "starlette" 599 | version = "0.14.2" 600 | description = "The little ASGI library that shines." 601 | category = "main" 602 | optional = false 603 | python-versions = ">=3.6" 604 | 605 | [package.extras] 606 | full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"] 607 | 608 | [[package]] 609 | name = "toml" 610 | version = "0.10.0" 611 | description = "Python Library for Tom's Obvious, Minimal Language" 612 | category = "dev" 613 | optional = false 614 | python-versions = "*" 615 | 616 | [[package]] 617 | name = "tox" 618 | version = "3.14.6" 619 | description = "tox is a generic virtualenv management and test command line tool" 620 | category = "dev" 621 | optional = false 622 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 623 | 624 | [package.dependencies] 625 | colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} 626 | filelock = ">=3.0.0,<4" 627 | importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} 628 | packaging = ">=14" 629 | pluggy = ">=0.12.0,<1" 630 | py = ">=1.4.17,<2" 631 | six = ">=1.14.0,<2" 632 | toml = ">=0.9.4" 633 | virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" 634 | 635 | [package.extras] 636 | docs = ["sphinx (>=2.0.0,<3)", "towncrier (>=18.5.0)", "pygments-github-lexers (>=0.0.5)", "sphinxcontrib-autoprogram (>=0.1.5)"] 637 | testing = ["freezegun (>=0.3.11,<1)", "pathlib2 (>=2.3.3,<3)", "pytest (>=4.0.0,<6)", "pytest-cov (>=2.5.1,<3)", "pytest-mock (>=1.10.0,<2)", "pytest-xdist (>=1.22.2,<2)", "pytest-randomly (>=1.0.0,<4)", "flaky (>=3.4.0,<4)", "psutil (>=5.6.1,<6)"] 638 | 639 | [[package]] 640 | name = "typed-ast" 641 | version = "1.4.1" 642 | description = "a fork of Python 2 and 3 ast modules with type comment support" 643 | category = "dev" 644 | optional = false 645 | python-versions = "*" 646 | 647 | [[package]] 648 | name = "typing-extensions" 649 | version = "3.7.4.2" 650 | description = "Backported and Experimental Type Hints for Python 3.5+" 651 | category = "dev" 652 | optional = false 653 | python-versions = "*" 654 | 655 | [[package]] 656 | name = "urllib3" 657 | version = "1.25.8" 658 | description = "HTTP library with thread-safe connection pooling, file post, and more." 659 | category = "dev" 660 | optional = false 661 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 662 | 663 | [package.extras] 664 | brotli = ["brotlipy (>=0.6.0)"] 665 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 666 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 667 | 668 | [[package]] 669 | name = "uvicorn" 670 | version = "0.11.3" 671 | description = "The lightning-fast ASGI server." 672 | category = "main" 673 | optional = false 674 | python-versions = "*" 675 | 676 | [package.dependencies] 677 | click = ">=7.0.0,<8.0.0" 678 | h11 = ">=0.8,<0.10" 679 | httptools = {version = ">=0.1.0,<0.2.0", markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""} 680 | uvloop = {version = ">=0.14.0", markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""} 681 | websockets = ">=8.0.0,<9.0.0" 682 | 683 | [[package]] 684 | name = "uvloop" 685 | version = "0.14.0" 686 | description = "Fast implementation of asyncio event loop on top of libuv" 687 | category = "main" 688 | optional = false 689 | python-versions = "*" 690 | 691 | [[package]] 692 | name = "virtualenv" 693 | version = "20.0.17" 694 | description = "Virtual Python Environment builder" 695 | category = "dev" 696 | optional = false 697 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 698 | 699 | [package.dependencies] 700 | appdirs = ">=1.4.3,<2" 701 | distlib = ">=0.3.0,<1" 702 | filelock = ">=3.0.0,<4" 703 | importlib-metadata = {version = ">=0.12,<2", markers = "python_version < \"3.8\""} 704 | six = ">=1.9.0,<2" 705 | 706 | [package.extras] 707 | docs = ["sphinx (>=2.0.0,<3)", "sphinx-argparse (>=0.2.5,<1)", "sphinx-rtd-theme (>=0.4.3,<1)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2,<1)"] 708 | testing = ["pytest (>=4.0.0,<6)", "coverage (>=4.5.1,<6)", "pytest-mock (>=2.0.0,<3)", "pytest-env (>=0.6.2,<1)", "pytest-timeout (>=1.3.4,<2)", "packaging (>=20.0)", "xonsh (>=0.9.16,<1)"] 709 | 710 | [[package]] 711 | name = "wcwidth" 712 | version = "0.1.9" 713 | description = "Measures number of Terminal column cells of wide-character codes" 714 | category = "dev" 715 | optional = false 716 | python-versions = "*" 717 | 718 | [[package]] 719 | name = "websockets" 720 | version = "8.1" 721 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 722 | category = "main" 723 | optional = false 724 | python-versions = ">=3.6.1" 725 | 726 | [[package]] 727 | name = "wrapt" 728 | version = "1.11.2" 729 | description = "Module for decorators, wrappers and monkey patching." 730 | category = "dev" 731 | optional = false 732 | python-versions = "*" 733 | 734 | [[package]] 735 | name = "zipp" 736 | version = "3.1.0" 737 | description = "Backport of pathlib-compatible object wrapper for zip files" 738 | category = "dev" 739 | optional = false 740 | python-versions = ">=3.6" 741 | 742 | [package.extras] 743 | docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] 744 | testing = ["jaraco.itertools", "func-timeout"] 745 | 746 | [metadata] 747 | lock-version = "1.1" 748 | python-versions = "^3.7" 749 | content-hash = "d8bb450fe3d34704f1b689e72bf36d72ce2661364b7821804300b92aec3c5006" 750 | 751 | [metadata.files] 752 | apipkg = [ 753 | {file = "apipkg-1.5-py2.py3-none-any.whl", hash = "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"}, 754 | {file = "apipkg-1.5.tar.gz", hash = "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6"}, 755 | ] 756 | appdirs = [ 757 | {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, 758 | {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, 759 | ] 760 | astroid = [ 761 | {file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"}, 762 | {file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"}, 763 | ] 764 | atomicwrites = [ 765 | {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, 766 | {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, 767 | ] 768 | attrs = [ 769 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 770 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 771 | ] 772 | autoflake = [ 773 | {file = "autoflake-1.3.1.tar.gz", hash = "sha256:680cb9dade101ed647488238ccb8b8bfb4369b53d58ba2c8cdf7d5d54e01f95b"}, 774 | ] 775 | bacon = [ 776 | {file = "bacon-0.2.0.zip", hash = "sha256:62bd99edd7a3d3e8fddbc7cd7e164313fc01cad6e84c4a010026aadc3129cc0f"}, 777 | ] 778 | bcrypt = [ 779 | {file = "bcrypt-3.1.7-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:d7bdc26475679dd073ba0ed2766445bb5b20ca4793ca0db32b399dccc6bc84b7"}, 780 | {file = "bcrypt-3.1.7-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:69361315039878c0680be456640f8705d76cb4a3a3fe1e057e0f261b74be4b31"}, 781 | {file = "bcrypt-3.1.7-cp27-cp27m-win32.whl", hash = "sha256:5432dd7b34107ae8ed6c10a71b4397f1c853bd39a4d6ffa7e35f40584cffd161"}, 782 | {file = "bcrypt-3.1.7-cp27-cp27m-win_amd64.whl", hash = "sha256:9fe92406c857409b70a38729dbdf6578caf9228de0aef5bc44f859ffe971a39e"}, 783 | {file = "bcrypt-3.1.7-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:763669a367869786bb4c8fcf731f4175775a5b43f070f50f46f0b59da45375d0"}, 784 | {file = "bcrypt-3.1.7-cp34-abi3-macosx_10_6_intel.whl", hash = "sha256:a190f2a5dbbdbff4b74e3103cef44344bc30e61255beb27310e2aec407766052"}, 785 | {file = "bcrypt-3.1.7-cp34-abi3-manylinux1_x86_64.whl", hash = "sha256:c9457fa5c121e94a58d6505cadca8bed1c64444b83b3204928a866ca2e599105"}, 786 | {file = "bcrypt-3.1.7-cp34-cp34m-win32.whl", hash = "sha256:8b10acde4e1919d6015e1df86d4c217d3b5b01bb7744c36113ea43d529e1c3de"}, 787 | {file = "bcrypt-3.1.7-cp34-cp34m-win_amd64.whl", hash = "sha256:cb93f6b2ab0f6853550b74e051d297c27a638719753eb9ff66d1e4072be67133"}, 788 | {file = "bcrypt-3.1.7-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:436a487dec749bca7e6e72498a75a5fa2433bda13bac91d023e18df9089ae0b8"}, 789 | {file = "bcrypt-3.1.7-cp35-cp35m-win32.whl", hash = "sha256:6fe49a60b25b584e2f4ef175b29d3a83ba63b3a4df1b4c0605b826668d1b6be5"}, 790 | {file = "bcrypt-3.1.7-cp35-cp35m-win_amd64.whl", hash = "sha256:a595c12c618119255c90deb4b046e1ca3bcfad64667c43d1166f2b04bc72db09"}, 791 | {file = "bcrypt-3.1.7-cp36-cp36m-win32.whl", hash = "sha256:74a015102e877d0ccd02cdeaa18b32aa7273746914a6c5d0456dd442cb65b99c"}, 792 | {file = "bcrypt-3.1.7-cp36-cp36m-win_amd64.whl", hash = "sha256:0258f143f3de96b7c14f762c770f5fc56ccd72f8a1857a451c1cd9a655d9ac89"}, 793 | {file = "bcrypt-3.1.7-cp37-cp37m-win32.whl", hash = "sha256:19a4b72a6ae5bb467fea018b825f0a7d917789bcfe893e53f15c92805d187294"}, 794 | {file = "bcrypt-3.1.7-cp37-cp37m-win_amd64.whl", hash = "sha256:ff032765bb8716d9387fd5376d987a937254b0619eff0972779515b5c98820bc"}, 795 | {file = "bcrypt-3.1.7-cp38-cp38-win32.whl", hash = "sha256:ce4e4f0deb51d38b1611a27f330426154f2980e66582dc5f438aad38b5f24fc1"}, 796 | {file = "bcrypt-3.1.7-cp38-cp38-win_amd64.whl", hash = "sha256:6305557019906466fc42dbc53b46da004e72fd7a551c044a827e572c82191752"}, 797 | {file = "bcrypt-3.1.7.tar.gz", hash = "sha256:0b0069c752ec14172c5f78208f1863d7ad6755a6fae6fe76ec2c80d13be41e42"}, 798 | ] 799 | black = [ 800 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 801 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 802 | ] 803 | certifi = [ 804 | {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, 805 | {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, 806 | ] 807 | cffi = [ 808 | {file = "cffi-1.14.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1cae98a7054b5c9391eb3249b86e0e99ab1e02bb0cc0575da191aedadbdf4384"}, 809 | {file = "cffi-1.14.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:cf16e3cf6c0a5fdd9bc10c21687e19d29ad1fe863372b5543deaec1039581a30"}, 810 | {file = "cffi-1.14.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f2b0fa0c01d8a0c7483afd9f31d7ecf2d71760ca24499c8697aeb5ca37dc090c"}, 811 | {file = "cffi-1.14.0-cp27-cp27m-win32.whl", hash = "sha256:99f748a7e71ff382613b4e1acc0ac83bf7ad167fb3802e35e90d9763daba4d78"}, 812 | {file = "cffi-1.14.0-cp27-cp27m-win_amd64.whl", hash = "sha256:c420917b188a5582a56d8b93bdd8e0f6eca08c84ff623a4c16e809152cd35793"}, 813 | {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:399aed636c7d3749bbed55bc907c3288cb43c65c4389964ad5ff849b6370603e"}, 814 | {file = "cffi-1.14.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:cab50b8c2250b46fe738c77dbd25ce017d5e6fb35d3407606e7a4180656a5a6a"}, 815 | {file = "cffi-1.14.0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:001bf3242a1bb04d985d63e138230802c6c8d4db3668fb545fb5005ddf5bb5ff"}, 816 | {file = "cffi-1.14.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:e56c744aa6ff427a607763346e4170629caf7e48ead6921745986db3692f987f"}, 817 | {file = "cffi-1.14.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b8c78301cefcf5fd914aad35d3c04c2b21ce8629b5e4f4e45ae6812e461910fa"}, 818 | {file = "cffi-1.14.0-cp35-cp35m-win32.whl", hash = "sha256:8c0ffc886aea5df6a1762d0019e9cb05f825d0eec1f520c51be9d198701daee5"}, 819 | {file = "cffi-1.14.0-cp35-cp35m-win_amd64.whl", hash = "sha256:8a6c688fefb4e1cd56feb6c511984a6c4f7ec7d2a1ff31a10254f3c817054ae4"}, 820 | {file = "cffi-1.14.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:95cd16d3dee553f882540c1ffe331d085c9e629499ceadfbda4d4fde635f4b7d"}, 821 | {file = "cffi-1.14.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66e41db66b47d0d8672d8ed2708ba91b2f2524ece3dee48b5dfb36be8c2f21dc"}, 822 | {file = "cffi-1.14.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:028a579fc9aed3af38f4892bdcc7390508adabc30c6af4a6e4f611b0c680e6ac"}, 823 | {file = "cffi-1.14.0-cp36-cp36m-win32.whl", hash = "sha256:cef128cb4d5e0b3493f058f10ce32365972c554572ff821e175dbc6f8ff6924f"}, 824 | {file = "cffi-1.14.0-cp36-cp36m-win_amd64.whl", hash = "sha256:337d448e5a725bba2d8293c48d9353fc68d0e9e4088d62a9571def317797522b"}, 825 | {file = "cffi-1.14.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e577934fc5f8779c554639376beeaa5657d54349096ef24abe8c74c5d9c117c3"}, 826 | {file = "cffi-1.14.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:62ae9af2d069ea2698bf536dcfe1e4eed9090211dbaafeeedf5cb6c41b352f66"}, 827 | {file = "cffi-1.14.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:14491a910663bf9f13ddf2bc8f60562d6bc5315c1f09c704937ef17293fb85b0"}, 828 | {file = "cffi-1.14.0-cp37-cp37m-win32.whl", hash = "sha256:c43866529f2f06fe0edc6246eb4faa34f03fe88b64a0a9a942561c8e22f4b71f"}, 829 | {file = "cffi-1.14.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2089ed025da3919d2e75a4d963d008330c96751127dd6f73c8dc0c65041b4c26"}, 830 | {file = "cffi-1.14.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3b911c2dbd4f423b4c4fcca138cadde747abdb20d196c4a48708b8a2d32b16dd"}, 831 | {file = "cffi-1.14.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:7e63cbcf2429a8dbfe48dcc2322d5f2220b77b2e17b7ba023d6166d84655da55"}, 832 | {file = "cffi-1.14.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3d311bcc4a41408cf5854f06ef2c5cab88f9fded37a3b95936c9879c1640d4c2"}, 833 | {file = "cffi-1.14.0-cp38-cp38-win32.whl", hash = "sha256:675686925a9fb403edba0114db74e741d8181683dcf216be697d208857e04ca8"}, 834 | {file = "cffi-1.14.0-cp38-cp38-win_amd64.whl", hash = "sha256:00789914be39dffba161cfc5be31b55775de5ba2235fe49aa28c148236c4e06b"}, 835 | {file = "cffi-1.14.0.tar.gz", hash = "sha256:2d384f4a127a15ba701207f7639d94106693b6cd64173d6c8988e2c25f3ac2b6"}, 836 | ] 837 | chardet = [ 838 | {file = "chardet-3.0.4-py2.py3-none-any.whl", hash = "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"}, 839 | {file = "chardet-3.0.4.tar.gz", hash = "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae"}, 840 | ] 841 | click = [ 842 | {file = "click-7.1.1-py2.py3-none-any.whl", hash = "sha256:e345d143d80bf5ee7534056164e5e112ea5e22716bbb1ce727941f4c8b471b9a"}, 843 | {file = "click-7.1.1.tar.gz", hash = "sha256:8a18b4ea89d8820c5d0c7da8a64b2c324b4dabb695804dbfea19b9be9d88c0cc"}, 844 | ] 845 | colorama = [ 846 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 847 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 848 | ] 849 | coverage = [ 850 | {file = "coverage-5.1-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65"}, 851 | {file = "coverage-5.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2"}, 852 | {file = "coverage-5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04"}, 853 | {file = "coverage-5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6"}, 854 | {file = "coverage-5.1-cp27-cp27m-win32.whl", hash = "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796"}, 855 | {file = "coverage-5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730"}, 856 | {file = "coverage-5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0"}, 857 | {file = "coverage-5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a"}, 858 | {file = "coverage-5.1-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf"}, 859 | {file = "coverage-5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9"}, 860 | {file = "coverage-5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768"}, 861 | {file = "coverage-5.1-cp35-cp35m-win32.whl", hash = "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2"}, 862 | {file = "coverage-5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7"}, 863 | {file = "coverage-5.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0"}, 864 | {file = "coverage-5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019"}, 865 | {file = "coverage-5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c"}, 866 | {file = "coverage-5.1-cp36-cp36m-win32.whl", hash = "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1"}, 867 | {file = "coverage-5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7"}, 868 | {file = "coverage-5.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355"}, 869 | {file = "coverage-5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489"}, 870 | {file = "coverage-5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd"}, 871 | {file = "coverage-5.1-cp37-cp37m-win32.whl", hash = "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e"}, 872 | {file = "coverage-5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a"}, 873 | {file = "coverage-5.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55"}, 874 | {file = "coverage-5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c"}, 875 | {file = "coverage-5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef"}, 876 | {file = "coverage-5.1-cp38-cp38-win32.whl", hash = "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24"}, 877 | {file = "coverage-5.1-cp38-cp38-win_amd64.whl", hash = "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0"}, 878 | {file = "coverage-5.1-cp39-cp39-win32.whl", hash = "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4"}, 879 | {file = "coverage-5.1-cp39-cp39-win_amd64.whl", hash = "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e"}, 880 | {file = "coverage-5.1.tar.gz", hash = "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052"}, 881 | ] 882 | distlib = [ 883 | {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, 884 | ] 885 | entrypoints = [ 886 | {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, 887 | {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, 888 | ] 889 | execnet = [ 890 | {file = "execnet-1.7.1-py2.py3-none-any.whl", hash = "sha256:d4efd397930c46415f62f8a31388d6be4f27a91d7550eb79bc64a756e0056547"}, 891 | {file = "execnet-1.7.1.tar.gz", hash = "sha256:cacb9df31c9680ec5f95553976c4da484d407e85e41c83cb812aa014f0eddc50"}, 892 | ] 893 | fastapi = [ 894 | {file = "fastapi-0.65.2-py3-none-any.whl", hash = "sha256:39569a18914075b2f1aaa03bcb9dc96a38e0e5dabaf3972e088c9077dfffa379"}, 895 | {file = "fastapi-0.65.2.tar.gz", hash = "sha256:8359e55d8412a5571c0736013d90af235d6949ec4ce978e9b63500c8f4b6f714"}, 896 | ] 897 | filelock = [ 898 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 899 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 900 | ] 901 | flake8 = [ 902 | {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, 903 | {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, 904 | ] 905 | h11 = [ 906 | {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, 907 | {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, 908 | ] 909 | httptools = [ 910 | {file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"}, 911 | {file = "httptools-0.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4"}, 912 | {file = "httptools-0.1.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6"}, 913 | {file = "httptools-0.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c"}, 914 | {file = "httptools-0.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a"}, 915 | {file = "httptools-0.1.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f"}, 916 | {file = "httptools-0.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2"}, 917 | {file = "httptools-0.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009"}, 918 | {file = "httptools-0.1.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437"}, 919 | {file = "httptools-0.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d"}, 920 | {file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"}, 921 | {file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"}, 922 | ] 923 | idna = [ 924 | {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, 925 | {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, 926 | ] 927 | importlib-metadata = [ 928 | {file = "importlib_metadata-1.6.0-py2.py3-none-any.whl", hash = "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f"}, 929 | {file = "importlib_metadata-1.6.0.tar.gz", hash = "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"}, 930 | ] 931 | isort = [ 932 | {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, 933 | {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, 934 | ] 935 | lazy-object-proxy = [ 936 | {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, 937 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, 938 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, 939 | {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, 940 | {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, 941 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, 942 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, 943 | {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, 944 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, 945 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, 946 | {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, 947 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, 948 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, 949 | {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, 950 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, 951 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, 952 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, 953 | {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, 954 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, 955 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, 956 | {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, 957 | ] 958 | mccabe = [ 959 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 960 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 961 | ] 962 | more-itertools = [ 963 | {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, 964 | {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, 965 | ] 966 | mypy = [ 967 | {file = "mypy-0.770-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:a34b577cdf6313bf24755f7a0e3f3c326d5c1f4fe7422d1d06498eb25ad0c600"}, 968 | {file = "mypy-0.770-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:86c857510a9b7c3104cf4cde1568f4921762c8f9842e987bc03ed4f160925754"}, 969 | {file = "mypy-0.770-cp35-cp35m-win_amd64.whl", hash = "sha256:a8ffcd53cb5dfc131850851cc09f1c44689c2812d0beb954d8138d4f5fc17f65"}, 970 | {file = "mypy-0.770-cp36-cp36m-macosx_10_6_x86_64.whl", hash = "sha256:7687f6455ec3ed7649d1ae574136835a4272b65b3ddcf01ab8704ac65616c5ce"}, 971 | {file = "mypy-0.770-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:3beff56b453b6ef94ecb2996bea101a08f1f8a9771d3cbf4988a61e4d9973761"}, 972 | {file = "mypy-0.770-cp36-cp36m-win_amd64.whl", hash = "sha256:15b948e1302682e3682f11f50208b726a246ab4e6c1b39f9264a8796bb416aa2"}, 973 | {file = "mypy-0.770-cp37-cp37m-macosx_10_6_x86_64.whl", hash = "sha256:b90928f2d9eb2f33162405f32dde9f6dcead63a0971ca8a1b50eb4ca3e35ceb8"}, 974 | {file = "mypy-0.770-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:c56ffe22faa2e51054c5f7a3bc70a370939c2ed4de308c690e7949230c995913"}, 975 | {file = "mypy-0.770-cp37-cp37m-win_amd64.whl", hash = "sha256:8dfb69fbf9f3aeed18afffb15e319ca7f8da9642336348ddd6cab2713ddcf8f9"}, 976 | {file = "mypy-0.770-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:219a3116ecd015f8dca7b5d2c366c973509dfb9a8fc97ef044a36e3da66144a1"}, 977 | {file = "mypy-0.770-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7ec45a70d40ede1ec7ad7f95b3c94c9cf4c186a32f6bacb1795b60abd2f9ef27"}, 978 | {file = "mypy-0.770-cp38-cp38-win_amd64.whl", hash = "sha256:f91c7ae919bbc3f96cd5e5b2e786b2b108343d1d7972ea130f7de27fdd547cf3"}, 979 | {file = "mypy-0.770-py3-none-any.whl", hash = "sha256:3b1fc683fb204c6b4403a1ef23f0b1fac8e4477091585e0c8c54cbdf7d7bb164"}, 980 | {file = "mypy-0.770.tar.gz", hash = "sha256:8a627507ef9b307b46a1fea9513d5c98680ba09591253082b4c48697ba05a4ae"}, 981 | ] 982 | mypy-extensions = [ 983 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 984 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 985 | ] 986 | packaging = [ 987 | {file = "packaging-20.3-py2.py3-none-any.whl", hash = "sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"}, 988 | {file = "packaging-20.3.tar.gz", hash = "sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3"}, 989 | ] 990 | passlib = [ 991 | {file = "passlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:68c35c98a7968850e17f1b6892720764cc7eed0ef2b7cb3116a89a28e43fe177"}, 992 | {file = "passlib-1.7.2.tar.gz", hash = "sha256:8d666cef936198bc2ab47ee9b0410c94adf2ba798e5a84bf220be079ae7ab6a8"}, 993 | ] 994 | pathspec = [ 995 | {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, 996 | {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, 997 | ] 998 | pluggy = [ 999 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 1000 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 1001 | ] 1002 | py = [ 1003 | {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, 1004 | {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, 1005 | ] 1006 | pycodestyle = [ 1007 | {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, 1008 | {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, 1009 | ] 1010 | pycparser = [ 1011 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 1012 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 1013 | ] 1014 | pydantic = [ 1015 | {file = "pydantic-1.7.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3c60039e84552442defbcb5d56711ef0e057028ca7bfc559374917408a88d84e"}, 1016 | {file = "pydantic-1.7.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6e7e314acb170e143c6f3912f93f2ec80a96aa2009ee681356b7ce20d57e5c62"}, 1017 | {file = "pydantic-1.7.4-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:8ef77cd17b73b5ba46788d040c0e820e49a2d80cfcd66fda3ba8be31094fd146"}, 1018 | {file = "pydantic-1.7.4-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:115d8aa6f257a1d469c66b6bfc7aaf04cd87c25095f24542065c68ebcb42fe63"}, 1019 | {file = "pydantic-1.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:66757d4e1eab69a3cfd3114480cc1d72b6dd847c4d30e676ae838c6740fdd146"}, 1020 | {file = "pydantic-1.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c92863263e4bd89e4f9cf1ab70d918170c51bd96305fe7b00853d80660acb26"}, 1021 | {file = "pydantic-1.7.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3b8154babf30a5e0fa3aa91f188356763749d9b30f7f211fafb247d4256d7877"}, 1022 | {file = "pydantic-1.7.4-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:80cc46378505f7ff202879dcffe4bfbf776c15675028f6e08d1d10bdfbb168ac"}, 1023 | {file = "pydantic-1.7.4-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:dda60d7878a5af2d8560c55c7c47a8908344aa78d32ec1c02d742ede09c534df"}, 1024 | {file = "pydantic-1.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:4c1979d5cc3e14b35f0825caddea5a243dd6085e2a7539c006bc46997ef7a61a"}, 1025 | {file = "pydantic-1.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8857576600c32aa488f18d30833aa833b54a48e3bab3adb6de97e463af71f8f8"}, 1026 | {file = "pydantic-1.7.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1f86d4da363badb39426a0ff494bf1d8510cd2f7274f460eee37bdbf2fd495ec"}, 1027 | {file = "pydantic-1.7.4-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:3ea1256a9e782149381e8200119f3e2edea7cd6b123f1c79ab4bbefe4d9ba2c9"}, 1028 | {file = "pydantic-1.7.4-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:e28455b42a0465a7bf2cde5eab530389226ce7dc779de28d17b8377245982b1e"}, 1029 | {file = "pydantic-1.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:47c5b1d44934375a3311891cabd450c150a31cf5c22e84aa172967bf186718be"}, 1030 | {file = "pydantic-1.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:00250e5123dd0b123ff72be0e1b69140e0b0b9e404d15be3846b77c6f1b1e387"}, 1031 | {file = "pydantic-1.7.4-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d24aa3f7f791a023888976b600f2f389d3713e4f23b7a4c88217d3fce61cdffc"}, 1032 | {file = "pydantic-1.7.4-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2c44a9afd4c4c850885436a4209376857989aaf0853c7b118bb2e628d4b78c4e"}, 1033 | {file = "pydantic-1.7.4-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:e87edd753da0ca1d44e308a1b1034859ffeab1f4a4492276bff9e1c3230db4fe"}, 1034 | {file = "pydantic-1.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:a3026ee105b5360855e500b4abf1a1d0b034d88e75a2d0d66a4c35e60858e15b"}, 1035 | {file = "pydantic-1.7.4-py3-none-any.whl", hash = "sha256:a82385c6d5a77e3387e94612e3e34b77e13c39ff1295c26e3ba664e7b98073e2"}, 1036 | {file = "pydantic-1.7.4.tar.gz", hash = "sha256:0a1abcbd525fbb52da58c813d54c2ec706c31a91afdb75411a73dd1dec036595"}, 1037 | ] 1038 | pyflakes = [ 1039 | {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, 1040 | {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, 1041 | ] 1042 | pyjwt = [ 1043 | {file = "PyJWT-1.7.1-py2.py3-none-any.whl", hash = "sha256:5c6eca3c2940464d106b99ba83b00c6add741c9becaec087fb7ccdefea71350e"}, 1044 | {file = "PyJWT-1.7.1.tar.gz", hash = "sha256:8d59a976fb773f3e6a39c85636357c4f0e242707394cadadd9814f5cbaa20e96"}, 1045 | ] 1046 | pylint = [ 1047 | {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, 1048 | {file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"}, 1049 | ] 1050 | pyparsing = [ 1051 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 1052 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 1053 | ] 1054 | pytest = [ 1055 | {file = "pytest-5.4.1-py3-none-any.whl", hash = "sha256:0e5b30f5cb04e887b91b1ee519fa3d89049595f428c1db76e73bd7f17b09b172"}, 1056 | {file = "pytest-5.4.1.tar.gz", hash = "sha256:84dde37075b8805f3d1f392cc47e38a0e59518fb46a431cfdaf7cf1ce805f970"}, 1057 | ] 1058 | pytest-cov = [ 1059 | {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, 1060 | {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, 1061 | ] 1062 | pytest-forked = [ 1063 | {file = "pytest-forked-1.1.3.tar.gz", hash = "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100"}, 1064 | {file = "pytest_forked-1.1.3-py2.py3-none-any.whl", hash = "sha256:1ae25dba8ee2e56fb47311c9638f9e58552691da87e82d25b0ce0e4bf52b7d87"}, 1065 | ] 1066 | pytest-xdist = [ 1067 | {file = "pytest-xdist-1.31.0.tar.gz", hash = "sha256:7dc0d027d258cd0defc618fb97055fbd1002735ca7a6d17037018cf870e24011"}, 1068 | {file = "pytest_xdist-1.31.0-py2.py3-none-any.whl", hash = "sha256:0f46020d3d9619e6d17a65b5b989c1ebbb58fc7b1da8fb126d70f4bac4dfeed1"}, 1069 | ] 1070 | python-multipart = [ 1071 | {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, 1072 | ] 1073 | regex = [ 1074 | {file = "regex-2020.4.4-cp27-cp27m-win32.whl", hash = "sha256:90742c6ff121a9c5b261b9b215cb476eea97df98ea82037ec8ac95d1be7a034f"}, 1075 | {file = "regex-2020.4.4-cp27-cp27m-win_amd64.whl", hash = "sha256:24f4f4062eb16c5bbfff6a22312e8eab92c2c99c51a02e39b4eae54ce8255cd1"}, 1076 | {file = "regex-2020.4.4-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:08119f707f0ebf2da60d2f24c2f39ca616277bb67ef6c92b72cbf90cbe3a556b"}, 1077 | {file = "regex-2020.4.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c9423a150d3a4fc0f3f2aae897a59919acd293f4cb397429b120a5fcd96ea3db"}, 1078 | {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:c087bff162158536387c53647411db09b6ee3f9603c334c90943e97b1052a156"}, 1079 | {file = "regex-2020.4.4-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1cbe0fa0b7f673400eb29e9ef41d4f53638f65f9a2143854de6b1ce2899185c3"}, 1080 | {file = "regex-2020.4.4-cp36-cp36m-win32.whl", hash = "sha256:0ce9537396d8f556bcfc317c65b6a0705320701e5ce511f05fc04421ba05b8a8"}, 1081 | {file = "regex-2020.4.4-cp36-cp36m-win_amd64.whl", hash = "sha256:7e1037073b1b7053ee74c3c6c0ada80f3501ec29d5f46e42669378eae6d4405a"}, 1082 | {file = "regex-2020.4.4-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4385f12aa289d79419fede43f979e372f527892ac44a541b5446617e4406c468"}, 1083 | {file = "regex-2020.4.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:a58dd45cb865be0ce1d5ecc4cfc85cd8c6867bea66733623e54bd95131f473b6"}, 1084 | {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ccccdd84912875e34c5ad2d06e1989d890d43af6c2242c6fcfa51556997af6cd"}, 1085 | {file = "regex-2020.4.4-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:ea4adf02d23b437684cd388d557bf76e3afa72f7fed5bbc013482cc00c816948"}, 1086 | {file = "regex-2020.4.4-cp37-cp37m-win32.whl", hash = "sha256:2294f8b70e058a2553cd009df003a20802ef75b3c629506be20687df0908177e"}, 1087 | {file = "regex-2020.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:e91ba11da11cf770f389e47c3f5c30473e6d85e06d7fd9dcba0017d2867aab4a"}, 1088 | {file = "regex-2020.4.4-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5635cd1ed0a12b4c42cce18a8d2fb53ff13ff537f09de5fd791e97de27b6400e"}, 1089 | {file = "regex-2020.4.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:23069d9c07e115537f37270d1d5faea3e0bdded8279081c4d4d607a2ad393683"}, 1090 | {file = "regex-2020.4.4-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:c162a21e0da33eb3d31a3ac17a51db5e634fc347f650d271f0305d96601dc15b"}, 1091 | {file = "regex-2020.4.4-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:fb95debbd1a824b2c4376932f2216cc186912e389bdb0e27147778cf6acb3f89"}, 1092 | {file = "regex-2020.4.4-cp38-cp38-win32.whl", hash = "sha256:2a3bf8b48f8e37c3a40bb3f854bf0121c194e69a650b209628d951190b862de3"}, 1093 | {file = "regex-2020.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bfed051dbff32fd8945eccca70f5e22b55e4148d2a8a45141a3b053d6455ae3"}, 1094 | {file = "regex-2020.4.4.tar.gz", hash = "sha256:295badf61a51add2d428a46b8580309c520d8b26e769868b922750cf3ce67142"}, 1095 | ] 1096 | requests = [ 1097 | {file = "requests-2.23.0-py2.7.egg", hash = "sha256:5d2d0ffbb515f39417009a46c14256291061ac01ba8f875b90cad137de83beb4"}, 1098 | {file = "requests-2.23.0-py2.py3-none-any.whl", hash = "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee"}, 1099 | {file = "requests-2.23.0.tar.gz", hash = "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"}, 1100 | ] 1101 | six = [ 1102 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 1103 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 1104 | ] 1105 | sqlalchemy = [ 1106 | {file = "SQLAlchemy-1.3.16-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:8d8c21e9d4efef01351bf28513648ceb988031be4159745a7ad1b3e28c8ff68a"}, 1107 | {file = "SQLAlchemy-1.3.16-cp27-cp27m-win32.whl", hash = "sha256:083e383a1dca8384d0ea6378bd182d83c600ed4ff4ec8247d3b2442cf70db1ad"}, 1108 | {file = "SQLAlchemy-1.3.16-cp27-cp27m-win_amd64.whl", hash = "sha256:128f6179325f7597a46403dde0bf148478f868df44841348dfc8d158e00db1f9"}, 1109 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:6056b671aeda3fc451382e52ab8a753c0d5f66ef2a5ccc8fa5ba7abd20988b4d"}, 1110 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7025c639ce7e170db845e94006cf5f404e243e6fc00d6c86fa19e8ad8d411880"}, 1111 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e18752cecaef61031252ca72031d4d6247b3212ebb84748fc5d1a0d2029c23ea"}, 1112 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-win32.whl", hash = "sha256:0a690a6486658d03cc6a73536d46e796b6570ac1f8a7ec133f9e28c448b69828"}, 1113 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-win_amd64.whl", hash = "sha256:d00b393f05dbd4ecd65c989b7f5a81110eae4baea7a6a4cdd94c20a908d1456e"}, 1114 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:114b6ace30001f056e944cebd46daef38fdb41ebb98f5e5940241a03ed6cad43"}, 1115 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:13d48cd8b925b6893a4e59b2dfb3e59a5204fd8c98289aad353af78bd214db49"}, 1116 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:211a1ce7e825f7142121144bac76f53ac28b12172716a710f4bf3eab477e730b"}, 1117 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-win32.whl", hash = "sha256:68d78cf4a9dfade2e6cf57c4be19f7b82ed66e67dacf93b32bb390c9bed12749"}, 1118 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-win_amd64.whl", hash = "sha256:2dc57ee80b76813759cccd1a7affedf9c4dbe5b065a91fb6092c9d8151d66078"}, 1119 | {file = "SQLAlchemy-1.3.16-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:43078c7ec0457387c79b8d52fff90a7ad352ca4c7aa841c366238c3e2cf52fdf"}, 1120 | {file = "SQLAlchemy-1.3.16-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb545da054e6297242a1bb1ba88e7a8ffb679f518258d66798ec712b82e4e07"}, 1121 | {file = "SQLAlchemy-1.3.16-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5b1bf3c2c2dca738235ce08079783ef04f1a7fc5b21cf24adaae77f2da4e73c3"}, 1122 | {file = "SQLAlchemy-1.3.16-cp38-cp38-win32.whl", hash = "sha256:3e625e283eecc15aee5b1ef77203bfb542563fa4a9aa622c7643c7b55438ff49"}, 1123 | {file = "SQLAlchemy-1.3.16-cp38-cp38-win_amd64.whl", hash = "sha256:7d98e0785c4cd7ae30b4a451416db71f5724a1839025544b4edbd92e00b91f0f"}, 1124 | {file = "SQLAlchemy-1.3.16.tar.gz", hash = "sha256:7224e126c00b8178dfd227bc337ba5e754b197a3867d33b9f30dc0208f773d70"}, 1125 | ] 1126 | starlette = [ 1127 | {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, 1128 | {file = "starlette-0.14.2.tar.gz", hash = "sha256:7d49f4a27f8742262ef1470608c59ddbc66baf37c148e938c7038e6bc7a998aa"}, 1129 | ] 1130 | toml = [ 1131 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 1132 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 1133 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 1134 | ] 1135 | tox = [ 1136 | {file = "tox-3.14.6-py2.py3-none-any.whl", hash = "sha256:b2c4b91c975ea5c11463d9ca00bebf82654439c5df0f614807b9bdec62cc9471"}, 1137 | {file = "tox-3.14.6.tar.gz", hash = "sha256:a4a6689045d93c208d77230853b28058b7513f5123647b67bf012f82fa168303"}, 1138 | ] 1139 | typed-ast = [ 1140 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 1141 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 1142 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 1143 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 1144 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 1145 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 1146 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 1147 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, 1148 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 1149 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 1150 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 1151 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 1152 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 1153 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, 1154 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 1155 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 1156 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 1157 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 1158 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 1159 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, 1160 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 1161 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 1162 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 1163 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, 1164 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, 1165 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, 1166 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, 1167 | {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, 1168 | {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, 1169 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 1170 | ] 1171 | typing-extensions = [ 1172 | {file = "typing_extensions-3.7.4.2-py2-none-any.whl", hash = "sha256:f8d2bd89d25bc39dabe7d23df520442fa1d8969b82544370e03d88b5a591c392"}, 1173 | {file = "typing_extensions-3.7.4.2-py3-none-any.whl", hash = "sha256:6e95524d8a547a91e08f404ae485bbb71962de46967e1b71a0cb89af24e761c5"}, 1174 | {file = "typing_extensions-3.7.4.2.tar.gz", hash = "sha256:79ee589a3caca649a9bfd2a8de4709837400dfa00b6cc81962a1e6a1815969ae"}, 1175 | ] 1176 | urllib3 = [ 1177 | {file = "urllib3-1.25.8-py2.py3-none-any.whl", hash = "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc"}, 1178 | {file = "urllib3-1.25.8.tar.gz", hash = "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc"}, 1179 | ] 1180 | uvicorn = [ 1181 | {file = "uvicorn-0.11.3-py3-none-any.whl", hash = "sha256:0f58170165c4495f563d8224b2f415a0829af0412baa034d6f777904613087fd"}, 1182 | {file = "uvicorn-0.11.3.tar.gz", hash = "sha256:6fdaf8e53bf1b2ddf0fe9ed06079b5348d7d1d87b3365fe2549e6de0d49e631c"}, 1183 | ] 1184 | uvloop = [ 1185 | {file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"}, 1186 | {file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"}, 1187 | {file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"}, 1188 | {file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"}, 1189 | {file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"}, 1190 | {file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"}, 1191 | {file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"}, 1192 | {file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"}, 1193 | {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"}, 1194 | ] 1195 | virtualenv = [ 1196 | {file = "virtualenv-20.0.17-py2.py3-none-any.whl", hash = "sha256:00cfe8605fb97f5a59d52baab78e6070e72c12ca64f51151695407cc0eb8a431"}, 1197 | {file = "virtualenv-20.0.17.tar.gz", hash = "sha256:c8364ec469084046c779c9a11ae6340094e8a0bf1d844330fc55c1cefe67c172"}, 1198 | ] 1199 | wcwidth = [ 1200 | {file = "wcwidth-0.1.9-py2.py3-none-any.whl", hash = "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1"}, 1201 | {file = "wcwidth-0.1.9.tar.gz", hash = "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"}, 1202 | ] 1203 | websockets = [ 1204 | {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, 1205 | {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, 1206 | {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, 1207 | {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, 1208 | {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, 1209 | {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, 1210 | {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, 1211 | {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, 1212 | {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, 1213 | {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, 1214 | {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, 1215 | {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, 1216 | {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, 1217 | {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, 1218 | {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, 1219 | {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, 1220 | {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, 1221 | {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, 1222 | {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, 1223 | {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, 1224 | {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, 1225 | {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, 1226 | ] 1227 | wrapt = [ 1228 | {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, 1229 | ] 1230 | zipp = [ 1231 | {file = "zipp-3.1.0-py3-none-any.whl", hash = "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b"}, 1232 | {file = "zipp-3.1.0.tar.gz", hash = "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"}, 1233 | ] 1234 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "example" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Tim Hughes"] 6 | 7 | packages = [ 8 | { include = "example", from = "src" }, 9 | ] 10 | 11 | [tool.poetry.scripts] 12 | fastapi-example = 'example.cli:main' 13 | 14 | [tool.poetry.dependencies] 15 | python = "^3.7" 16 | fastapi = "^0.65.2" 17 | sqlalchemy = "^1.3.16" 18 | PyJWT = "^1.7.1" 19 | bcrypt = "^3.1.7" 20 | python-multipart = "^0.0.5" 21 | uvicorn = "^0.11.3" 22 | passlib = "^1.7.2" 23 | 24 | [tool.poetry.dev-dependencies] 25 | pytest = "^5.2" 26 | tox = "^3.14.6" 27 | flake8 = "^3.7.9" 28 | pylint = "^2.4.4" 29 | pytest-cov = "^2.8.1" 30 | pytest-xdist = "^1.31.0" 31 | mypy = "^0.770" 32 | requests = "^2.23.0" 33 | autoflake = "^1.3.1" 34 | black = "^19.10b0" 35 | 36 | 37 | [build-system] 38 | requires = ["poetry>=0.12"] 39 | build-backend = "poetry.masonry.api" 40 | -------------------------------------------------------------------------------- /src/example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timhughes/example-fastapi-sqlachemy-pytest/0e95544c2fa8418bf17653089a7667d61a8b8b5b/src/example/__init__.py -------------------------------------------------------------------------------- /src/example/cli.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # vim:fenc=utf-8 4 | # 5 | # Copyright © 2020 Tim Hughes 6 | # 7 | # Distributed under terms of the MIT license. 8 | 9 | 10 | import argparse 11 | import logging 12 | import sys 13 | 14 | import uvicorn 15 | from example.main import app 16 | 17 | logging.basicConfig(level=logging.INFO) 18 | logger = logging.getLogger(sys.argv[0]) 19 | 20 | 21 | def serve(args): 22 | logger.info(f"Running server {args}") 23 | uvicorn.run(app, host="0.0.0.0", port=8000) 24 | 25 | 26 | def main(): 27 | parser = argparse.ArgumentParser() 28 | subparsers = parser.add_subparsers() 29 | 30 | parser_server = subparsers.add_parser("serve", help="start the server") 31 | parser_server.add_argument("--config", help="path to configuration file") 32 | parser_server.set_defaults(func=serve) 33 | 34 | args = parser.parse_args() 35 | serve(args) 36 | 37 | 38 | if __name__ == "__main__": 39 | try: 40 | main() 41 | except KeyboardInterrupt: 42 | logger.info("Exiting on KeyboardInterrupt") 43 | except Exception as exc: 44 | logger.info(f"Exiting on unknown error {exc}") 45 | finally: 46 | pass 47 | -------------------------------------------------------------------------------- /src/example/crud/__init__.py: -------------------------------------------------------------------------------- 1 | from . import item -------------------------------------------------------------------------------- /src/example/crud/item.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | 3 | from fastapi.encoders import jsonable_encoder 4 | from sqlalchemy.orm import Session 5 | 6 | from ..models.item import ItemModel 7 | from ..schemas.item import ItemCreateSchema, ItemSchema, ItemUpdateSchema 8 | 9 | 10 | def get(db_session: Session, *, item_id: int) -> ItemModel: 11 | return db_session.query(ItemModel).filter(ItemModel.id == item_id).first() 12 | 13 | 14 | def get_by_name(db_session: Session, *, name: str) -> Optional[ItemModel]: 15 | return db_session.query(ItemModel).filter(ItemModel.name == name).first() 16 | 17 | 18 | def list(db_session: Session, *, skip=0, limit=100) -> List[ItemSchema]: 19 | return db_session.query(ItemModel).offset(skip).limit(limit).all() 20 | 21 | 22 | def create(db_session: Session, *, obj_in: ItemCreateSchema, ) -> ItemModel: 23 | db_obj = ItemModel(**obj_in.dict()) 24 | db_session.add(db_obj) 25 | db_session.commit() 26 | db_session.refresh(db_obj) 27 | return db_obj 28 | 29 | 30 | def update( 31 | db_session: Session, *, db_obj: ItemModel, obj_in: ItemUpdateSchema 32 | ) -> ItemModel: 33 | obj_data = jsonable_encoder(db_obj) 34 | update_data = obj_in.dict(exclude_unset=True) 35 | for field in obj_data: 36 | if field in update_data: 37 | setattr(db_obj, field, update_data[field]) 38 | db_session.add(db_obj) 39 | db_session.commit() 40 | db_session.refresh(db_obj) 41 | return db_obj 42 | -------------------------------------------------------------------------------- /src/example/db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timhughes/example-fastapi-sqlachemy-pytest/0e95544c2fa8418bf17653089a7667d61a8b8b5b/src/example/db/__init__.py -------------------------------------------------------------------------------- /src/example/db/base.py: -------------------------------------------------------------------------------- 1 | 2 | # noinspection PyUnresolvedReferences 3 | from .session import DBBase # noqa: 4 | 5 | # noinspection PyUnresolvedReferences 6 | from ..models.item import ItemModel # noqa: 7 | -------------------------------------------------------------------------------- /src/example/db/session.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from sqlalchemy.orm import sessionmaker, scoped_session 4 | 5 | from example import settings 6 | 7 | engine = create_engine( 8 | settings.SQLALCHEMY_DATABASE_URL, 9 | pool_pre_ping=True, 10 | connect_args={"check_same_thread": False}, 11 | ) 12 | 13 | db_session = scoped_session( 14 | sessionmaker(autocommit=False, autoflush=False, bind=engine) 15 | ) 16 | Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) 17 | 18 | DBBase = declarative_base() 19 | 20 | -------------------------------------------------------------------------------- /src/example/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from starlette.requests import Request 3 | 4 | from . import settings 5 | from .db import base # noqa: 6 | from .db.session import Session 7 | from .routes import router as api_router 8 | 9 | 10 | def get_application() -> FastAPI: 11 | application = FastAPI( 12 | title=settings.PROJECT_NAME, debug=settings.DEBUG, 13 | version=settings.VERSION 14 | ) 15 | application.include_router( 16 | api_router, prefix=settings.API_PREFIX, 17 | ) 18 | return application 19 | 20 | 21 | app = get_application() 22 | 23 | 24 | @app.middleware("http") 25 | async def db_session_middleware(request: Request, call_next): 26 | request.state.db = Session() 27 | response = await call_next(request) 28 | request.state.db.close() 29 | return response 30 | -------------------------------------------------------------------------------- /src/example/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timhughes/example-fastapi-sqlachemy-pytest/0e95544c2fa8418bf17653089a7667d61a8b8b5b/src/example/models/__init__.py -------------------------------------------------------------------------------- /src/example/models/item.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Integer, String, Boolean 2 | from sqlalchemy.orm import relationship 3 | 4 | from example.db.session import DBBase 5 | 6 | 7 | class ItemModel(DBBase): 8 | __tablename__ = "items" 9 | 10 | id = Column(Integer, primary_key=True, index=True) 11 | name = Column(String, unique=True, index=True) 12 | description = Column(String) 13 | -------------------------------------------------------------------------------- /src/example/routers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timhughes/example-fastapi-sqlachemy-pytest/0e95544c2fa8418bf17653089a7667d61a8b8b5b/src/example/routers/__init__.py -------------------------------------------------------------------------------- /src/example/routers/item.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from example import crud 4 | from example.schemas.item import ItemSchema, ItemCreateSchema 5 | from fastapi import APIRouter, Depends 6 | from sqlalchemy.orm import Session 7 | 8 | from .utils.db import get_db 9 | 10 | router = APIRouter() 11 | 12 | logger = logging.getLogger('example') 13 | 14 | 15 | @router.post("/", response_model=ItemSchema, name="item:create") 16 | async def item_create( 17 | *, 18 | db: Session = Depends(get_db), 19 | item_in: ItemCreateSchema, 20 | ): 21 | return crud.item.create( 22 | db_session=db, 23 | obj_in=item_in, 24 | ) 25 | 26 | 27 | @router.get("/{slug}", response_model=ItemSchema, name="item:get") 28 | async def item_get( 29 | *, 30 | db: Session = Depends(get_db), 31 | slug: str, 32 | ): 33 | raise NotImplementedError 34 | 35 | 36 | @router.put("/{slug}", response_model=ItemSchema, name="item:update") 37 | async def item_update( 38 | *, 39 | db: Session = Depends(get_db), 40 | slug: str, 41 | ): 42 | raise NotImplementedError 43 | 44 | 45 | @router.delete("/{slug}", name="item:archive") 46 | async def item_archive( 47 | *, 48 | db: Session = Depends(get_db), 49 | slug: str, 50 | ): 51 | raise NotImplementedError 52 | -------------------------------------------------------------------------------- /src/example/routers/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timhughes/example-fastapi-sqlachemy-pytest/0e95544c2fa8418bf17653089a7667d61a8b8b5b/src/example/routers/utils/__init__.py -------------------------------------------------------------------------------- /src/example/routers/utils/db.py: -------------------------------------------------------------------------------- 1 | from starlette.requests import Request 2 | 3 | 4 | def get_db(request: Request): 5 | return request.state.db 6 | -------------------------------------------------------------------------------- /src/example/routes.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | from .routers import item 4 | 5 | router = APIRouter() 6 | router.include_router(item.router, tags=["item"], prefix="/item") 7 | -------------------------------------------------------------------------------- /src/example/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timhughes/example-fastapi-sqlachemy-pytest/0e95544c2fa8418bf17653089a7667d61a8b8b5b/src/example/schemas/__init__.py -------------------------------------------------------------------------------- /src/example/schemas/item.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | # ================================================= 7 | # Base classes should only be used to inherit from. 8 | # ================================================= 9 | 10 | # Shared properties 11 | class ItemBaseSchema(BaseModel): 12 | name: Optional[str] 13 | description: Optional[str] = None 14 | 15 | 16 | # Properties needed in DB only 17 | class ItemBaseInDBSchema(ItemBaseSchema): 18 | id: int 19 | class Config: 20 | orm_mode = True 21 | 22 | 23 | # =========================================================== 24 | # Tables below here are for use in the rest of the code base. 25 | # =========================================================== 26 | 27 | # Additional properties stored in DB 28 | class ItemInDBSchema(ItemBaseInDBSchema): 29 | pass 30 | 31 | 32 | # Properties to receive via API on creation 33 | class ItemCreateSchema(ItemBaseSchema): 34 | pass 35 | 36 | 37 | # Properties to receive via API on update 38 | class ItemUpdateSchema(ItemBaseSchema): 39 | # group_id: Optional[int] = None 40 | pass 41 | 42 | 43 | # Additional properties to return to client via API 44 | class ItemSchema(ItemBaseSchema): 45 | class Config: 46 | orm_mode = True 47 | -------------------------------------------------------------------------------- /src/example/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | PROJECT_NAME = "fastapi sqlalchemy pytest example" 4 | VERSION = "0.0.1" 5 | BASE_DIR: str = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 6 | API_PREFIX = "/api" 7 | SQLALCHEMY_DATABASE_URL: str = os.getenv('DATABASE_URI', f"sqlite:///{BASE_DIR}/foo.db") 8 | DEBUG=True -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any, Generator 3 | 4 | import pytest 5 | from example.db.session import DBBase 6 | from example.main import get_application 7 | from example.routers.utils.db import get_db 8 | from fastapi import FastAPI 9 | from fastapi.testclient import TestClient 10 | from sqlalchemy import create_engine 11 | from sqlalchemy.orm import sessionmaker 12 | 13 | # Default to using sqlite in memory for fast tests. 14 | # Can be overridden by environment variable for testing in CI against other 15 | # database engines 16 | SQLALCHEMY_DATABASE_URL = os.getenv('TEST_DATABASE_URL', "sqlite://") 17 | 18 | engine = create_engine( 19 | SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False} 20 | ) 21 | 22 | Session = sessionmaker(autocommit=False, autoflush=False, bind=engine) 23 | 24 | 25 | @pytest.fixture(autouse=True) 26 | def app() -> Generator[FastAPI, Any, None]: 27 | """ 28 | Create a fresh database on each test case. 29 | """ 30 | DBBase.metadata.create_all(engine) # Create the tables. 31 | _app = get_application() 32 | yield _app 33 | DBBase.metadata.drop_all(engine) 34 | 35 | 36 | @pytest.fixture 37 | def db_session(app: FastAPI) -> Generator[Session, Any, None]: 38 | """ 39 | Creates a fresh sqlalchemy session for each test that operates in a 40 | transaction. The transaction is rolled back at the end of each test ensuring 41 | a clean state. 42 | """ 43 | 44 | # connect to the database 45 | connection = engine.connect() 46 | # begin a non-ORM transaction 47 | transaction = connection.begin() 48 | # bind an individual Session to the connection 49 | session = Session(bind=connection) 50 | yield session # use the session in tests. 51 | session.close() 52 | # rollback - everything that happened with the 53 | # Session above (including calls to commit()) 54 | # is rolled back. 55 | transaction.rollback() 56 | # return connection to the Engine 57 | connection.close() 58 | 59 | 60 | @pytest.fixture() 61 | def client(app: FastAPI, db_session: Session) -> Generator[TestClient, Any, None]: 62 | """ 63 | Create a new FastAPI TestClient that uses the `db_session` fixture to override 64 | the `get_db` dependency that is injected into routes. 65 | """ 66 | 67 | def _get_test_db(): 68 | try: 69 | yield db_session 70 | finally: 71 | pass 72 | 73 | app.dependency_overrides[get_db] = _get_test_db 74 | with TestClient(app) as client: 75 | yield client 76 | -------------------------------------------------------------------------------- /tests/test_crud_item.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | 3 | from example import crud 4 | from example.models.item import ItemModel 5 | from example.schemas.item import ItemCreateSchema, ItemUpdateSchema 6 | 7 | 8 | def test_item_get(db_session, ): 9 | obj_in = ItemCreateSchema(name="test_item", description="something cool") 10 | item: ItemModel = crud.item.create( 11 | db_session, obj_in=obj_in, 12 | ) 13 | item2: ItemModel = crud.item.get(db_session, item_id=item.id) 14 | assert item == item2 15 | 16 | 17 | def test_item_get_by_name(db_session, ): 18 | obj_in = ItemCreateSchema(name="test_item", description="something cool") 19 | item: ItemModel = crud.item.create( 20 | db_session, obj_in=obj_in, 21 | ) 22 | item2: ItemModel = crud.item.get_by_name(db_session, name="test_item") 23 | assert item == item2 24 | 25 | def test_item_list(db_session, ): 26 | for i in range(1, 20): 27 | item = ItemCreateSchema(name=f"item{i}", 28 | description=f"something cool {i}") 29 | crud.item.create(db_session, obj_in=item) 30 | item_list = crud.item.list(db_session, skip=5, limit=8) 31 | for i, item in enumerate(item_list, start=6): 32 | assert item.name == f"item{i}" 33 | assert len(item_list) == 8 34 | 35 | def test_item_create(db_session, ): 36 | obj_in = ItemCreateSchema(name="test_item", description="something cool") 37 | item: ItemModel = crud.item.create( 38 | db_session, obj_in=obj_in, 39 | ) 40 | assert item.name == "test_item" 41 | 42 | 43 | def test_item_update(db_session, ): 44 | obj_setup = ItemCreateSchema(name="test_item", description="something cool") 45 | item: ItemModel = crud.item.create( 46 | db_session, obj_in=obj_setup, 47 | ) 48 | obj_in = ItemUpdateSchema(name="test_item_updated") 49 | item_updated: ItemModel = crud.item.update( 50 | db_session, db_obj=copy(item), obj_in=obj_in 51 | ) 52 | assert item_updated.name == "test_item_updated" 53 | assert item_updated is not item 54 | -------------------------------------------------------------------------------- /tests/test_routers_item.py: -------------------------------------------------------------------------------- 1 | from example.schemas.item import ItemCreateSchema, ItemSchema 2 | from fastapi import FastAPI 3 | from fastapi.encoders import jsonable_encoder 4 | from fastapi.testclient import TestClient 5 | from sqlalchemy.orm import Session 6 | 7 | 8 | 9 | def test_group_create( 10 | app: FastAPI, 11 | db_session: Session, 12 | client: TestClient, 13 | ): 14 | group_schema = ItemCreateSchema( 15 | name="example", description="this is an example group", 16 | ) 17 | req_data = jsonable_encoder(group_schema) 18 | response = client.post( 19 | "/api/item/", 20 | json=req_data, 21 | ) 22 | data = response.json() 23 | print(data) 24 | assert response.status_code == 200 25 | assert ItemSchema(**data) 26 | --------------------------------------------------------------------------------