├── tests ├── __init__.py └── test_app.py ├── app ├── config │ ├── __init__.py │ └── database.py ├── models │ ├── __init__.py │ └── foo.py ├── routers │ ├── __init__.py │ └── foo.py ├── schemas │ ├── __init__.py │ └── foo.py ├── utils │ ├── __init__.py │ ├── get_list_of_app_exceptions_for_frontend.py │ ├── request_exceptions.py │ ├── service_result.py │ └── app_exceptions.py ├── services │ ├── __init__.py │ ├── main.py │ └── foo.py ├── __init__.py └── main.py ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── Makefile ├── pyproject.toml ├── LICENSE └── poetry.lock /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/config/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/routers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/services/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.1.0' 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | app.db 3 | .pytest_cache -------------------------------------------------------------------------------- /tests/test_app.py: -------------------------------------------------------------------------------- 1 | from app import __version__ 2 | 3 | 4 | def test_version(): 5 | assert __version__ == "0.1.0" 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.analysis.extraPaths": ["./app"], 3 | "python.venvPath": "~/.cache/pypoetry/virtualenvs/" 4 | } 5 | -------------------------------------------------------------------------------- /app/utils/get_list_of_app_exceptions_for_frontend.py: -------------------------------------------------------------------------------- 1 | from utils.app_exceptions import AppException 2 | 3 | print([e for e in dir(AppException) if "__" not in e]) 4 | # ['FooCreateItem', 'FooGetItem', 'FooItemRequiresAuth'] 5 | -------------------------------------------------------------------------------- /app/services/main.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.orm import Session 2 | 3 | 4 | class DBSessionContext(object): 5 | def __init__(self, db: Session): 6 | self.db = db 7 | 8 | 9 | class AppService(DBSessionContext): 10 | pass 11 | 12 | 13 | class AppCRUD(DBSessionContext): 14 | pass 15 | -------------------------------------------------------------------------------- /app/schemas/foo.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class FooItemBase(BaseModel): 5 | description: str 6 | 7 | 8 | class FooItemCreate(FooItemBase): 9 | public: bool 10 | 11 | 12 | class FooItem(FooItemBase): 13 | id: int 14 | 15 | class Config: 16 | orm_mode = True 17 | -------------------------------------------------------------------------------- /app/models/foo.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Boolean, Column, Integer, String 2 | 3 | from config.database import Base 4 | 5 | 6 | class FooItem(Base): 7 | __tablename__ = "foo_items" 8 | 9 | id = Column(Integer, primary_key=True, index=True) 10 | description = Column(String) 11 | public = Column(Boolean, default=False) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Abstracting FastAPI Services 2 | 3 | See this article for more information: 4 | 5 | ## Poetry 6 | 7 | ```bash 8 | poetry install 9 | ``` 10 | 11 | ## Makefile 12 | 13 | ```bash 14 | make dev # run uvicorn with restart 15 | make test # run pytest 16 | make create-items # create items for illustration purposes 17 | make get-items # retrieve items for illustration purposes 18 | ``` 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dev: 2 | cd app && poetry run uvicorn main:app --reload 3 | 4 | test: 5 | poetry run pytest 6 | 7 | create-items: 8 | curl -X POST localhost:8000/foo/item/ --data '{"description":"some item description", "public":false}' && echo 9 | curl -X POST localhost:8000/foo/item/ --data '{"description":"some item description", "public":true}' 10 | 11 | get-items: 12 | curl -X GET localhost:8000/foo/item/1 && echo 13 | curl -X GET localhost:8000/foo/item/2 -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastapi-service" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Camillo Visini "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | fastapi = "^0.62.0" 10 | loguru = "^0.5.3" 11 | SQLAlchemy = "^1.3.20" 12 | 13 | [tool.poetry.dev-dependencies] 14 | pytest = "^5.2" 15 | black = "^20.8b1" 16 | flake8 = "^3.8.4" 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /app/config/database.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import create_engine 2 | from sqlalchemy.ext.declarative import declarative_base 3 | from sqlalchemy.orm import sessionmaker 4 | 5 | SQLALCHEMY_DATABASE_URL = "sqlite:///./app.db" 6 | 7 | engine = create_engine( 8 | SQLALCHEMY_DATABASE_URL, 9 | connect_args={"check_same_thread": False}, 10 | ) 11 | SessionLocal = sessionmaker( 12 | autocommit=False, 13 | autoflush=False, 14 | bind=engine, 15 | ) 16 | 17 | Base = declarative_base() 18 | 19 | 20 | def get_db(): 21 | db = SessionLocal() 22 | try: 23 | yield db 24 | finally: 25 | db.close() 26 | 27 | 28 | def create_tables(): 29 | Base.metadata.create_all(bind=engine) 30 | -------------------------------------------------------------------------------- /app/routers/foo.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | 3 | from services.foo import FooService 4 | from schemas.foo import FooItem, FooItemCreate 5 | 6 | from utils.service_result import handle_result 7 | 8 | from config.database import get_db 9 | 10 | router = APIRouter( 11 | prefix="/foo", 12 | tags=["items"], 13 | responses={404: {"description": "Not found"}}, 14 | ) 15 | 16 | 17 | @router.post("/item/", response_model=FooItem) 18 | async def create_item(item: FooItemCreate, db: get_db = Depends()): 19 | result = FooService(db).create_item(item) 20 | return handle_result(result) 21 | 22 | 23 | @router.get("/item/{item_id}", response_model=FooItem) 24 | async def get_item(item_id: int, db: get_db = Depends()): 25 | result = FooService(db).get_item(item_id) 26 | return handle_result(result) 27 | -------------------------------------------------------------------------------- /app/utils/request_exceptions.py: -------------------------------------------------------------------------------- 1 | from fastapi.encoders import jsonable_encoder 2 | from fastapi.exceptions import RequestValidationError 3 | 4 | from starlette.exceptions import HTTPException 5 | from starlette.requests import Request 6 | from starlette.responses import JSONResponse 7 | from starlette.status import HTTP_422_UNPROCESSABLE_ENTITY 8 | 9 | 10 | async def http_exception_handler( 11 | request: Request, exc: HTTPException 12 | ) -> JSONResponse: 13 | return JSONResponse({"detail": exc.detail}, status_code=exc.status_code) 14 | 15 | 16 | async def request_validation_exception_handler( 17 | request: Request, exc: RequestValidationError 18 | ) -> JSONResponse: 19 | return JSONResponse( 20 | status_code=HTTP_422_UNPROCESSABLE_ENTITY, 21 | content={"detail": jsonable_encoder(exc.errors())}, 22 | ) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Camillo Visini 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 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | from utils.app_exceptions import AppExceptionCase 2 | from fastapi import FastAPI 3 | 4 | from routers import foo 5 | from config.database import create_tables 6 | 7 | from fastapi.exceptions import RequestValidationError 8 | from starlette.exceptions import HTTPException as StarletteHTTPException 9 | 10 | from utils.request_exceptions import ( 11 | http_exception_handler, 12 | request_validation_exception_handler, 13 | ) 14 | from utils.app_exceptions import app_exception_handler 15 | 16 | create_tables() 17 | 18 | 19 | app = FastAPI() 20 | 21 | 22 | @app.exception_handler(StarletteHTTPException) 23 | async def custom_http_exception_handler(request, e): 24 | return await http_exception_handler(request, e) 25 | 26 | 27 | @app.exception_handler(RequestValidationError) 28 | async def custom_validation_exception_handler(request, e): 29 | return await request_validation_exception_handler(request, e) 30 | 31 | 32 | @app.exception_handler(AppExceptionCase) 33 | async def custom_app_exception_handler(request, e): 34 | return await app_exception_handler(request, e) 35 | 36 | 37 | app.include_router(foo.router) 38 | 39 | 40 | @app.get("/") 41 | async def root(): 42 | return {"message": "Hello World"} 43 | -------------------------------------------------------------------------------- /app/services/foo.py: -------------------------------------------------------------------------------- 1 | from schemas.foo import FooItemCreate 2 | from utils.app_exceptions import AppException 3 | 4 | from services.main import AppService, AppCRUD 5 | from models.foo import FooItem 6 | from utils.service_result import ServiceResult 7 | 8 | 9 | class FooService(AppService): 10 | def create_item(self, item: FooItemCreate) -> ServiceResult: 11 | foo_item = FooCRUD(self.db).create_item(item) 12 | if not foo_item: 13 | return ServiceResult(AppException.FooCreateItem()) 14 | return ServiceResult(foo_item) 15 | 16 | def get_item(self, item_id: int) -> ServiceResult: 17 | foo_item = FooCRUD(self.db).get_item(item_id) 18 | if not foo_item: 19 | return ServiceResult(AppException.FooGetItem({"item_id": item_id})) 20 | if not foo_item.public: 21 | return ServiceResult(AppException.FooItemRequiresAuth()) 22 | return ServiceResult(foo_item) 23 | 24 | 25 | class FooCRUD(AppCRUD): 26 | def create_item(self, item: FooItemCreate) -> FooItem: 27 | foo_item = FooItem(description=item.description, public=item.public) 28 | self.db.add(foo_item) 29 | self.db.commit() 30 | self.db.refresh(foo_item) 31 | return foo_item 32 | 33 | def get_item(self, item_id: int) -> FooItem: 34 | foo_item = self.db.query(FooItem).filter(FooItem.id == item_id).first() 35 | if foo_item: 36 | return foo_item 37 | return None 38 | -------------------------------------------------------------------------------- /app/utils/service_result.py: -------------------------------------------------------------------------------- 1 | from loguru import logger 2 | import inspect 3 | 4 | from utils.app_exceptions import AppExceptionCase 5 | 6 | 7 | class ServiceResult(object): 8 | def __init__(self, arg): 9 | if isinstance(arg, AppExceptionCase): 10 | self.success = False 11 | self.exception_case = arg.exception_case 12 | self.status_code = arg.status_code 13 | else: 14 | self.success = True 15 | self.exception_case = None 16 | self.status_code = None 17 | self.value = arg 18 | 19 | def __str__(self): 20 | if self.success: 21 | return "[Success]" 22 | return f'[Exception] "{self.exception_case}"' 23 | 24 | def __repr__(self): 25 | if self.success: 26 | return "" 27 | return f"" 28 | 29 | def __enter__(self): 30 | return self.value 31 | 32 | def __exit__(self, *kwargs): 33 | pass 34 | 35 | 36 | def caller_info() -> str: 37 | info = inspect.getframeinfo(inspect.stack()[2][0]) 38 | return f"{info.filename}:{info.function}:{info.lineno}" 39 | 40 | 41 | def handle_result(result: ServiceResult): 42 | if not result.success: 43 | with result as exception: 44 | logger.error(f"{exception} | caller={caller_info()}") 45 | raise exception 46 | with result as result: 47 | return result 48 | -------------------------------------------------------------------------------- /app/utils/app_exceptions.py: -------------------------------------------------------------------------------- 1 | from fastapi import Request 2 | from starlette.responses import JSONResponse 3 | 4 | 5 | class AppExceptionCase(Exception): 6 | def __init__(self, status_code: int, context: dict): 7 | self.exception_case = self.__class__.__name__ 8 | self.status_code = status_code 9 | self.context = context 10 | 11 | def __str__(self): 12 | return ( 13 | f"" 15 | ) 16 | 17 | 18 | async def app_exception_handler(request: Request, exc: AppExceptionCase): 19 | return JSONResponse( 20 | status_code=exc.status_code, 21 | content={ 22 | "app_exception": exc.exception_case, 23 | "context": exc.context, 24 | }, 25 | ) 26 | 27 | 28 | class AppException(object): 29 | class FooCreateItem(AppExceptionCase): 30 | def __init__(self, context: dict = None): 31 | """ 32 | Item creation failed 33 | """ 34 | status_code = 500 35 | AppExceptionCase.__init__(self, status_code, context) 36 | 37 | class FooGetItem(AppExceptionCase): 38 | def __init__(self, context: dict = None): 39 | """ 40 | Item not found 41 | """ 42 | status_code = 404 43 | AppExceptionCase.__init__(self, status_code, context) 44 | 45 | class FooItemRequiresAuth(AppExceptionCase): 46 | def __init__(self, context: dict = None): 47 | """ 48 | Item is not public and requires auth 49 | """ 50 | status_code = 401 51 | AppExceptionCase.__init__(self, status_code, context) 52 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "atomicwrites" 11 | version = "1.4.0" 12 | description = "Atomic file writes." 13 | category = "dev" 14 | optional = false 15 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 16 | 17 | [[package]] 18 | name = "attrs" 19 | version = "20.3.0" 20 | description = "Classes Without Boilerplate" 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | 25 | [package.extras] 26 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "furo", "sphinx", "pre-commit"] 27 | docs = ["furo", "sphinx", "zope.interface"] 28 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 29 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six"] 30 | 31 | [[package]] 32 | name = "black" 33 | version = "20.8b1" 34 | description = "The uncompromising code formatter." 35 | category = "dev" 36 | optional = false 37 | python-versions = ">=3.6" 38 | 39 | [package.dependencies] 40 | appdirs = "*" 41 | click = ">=7.1.2" 42 | mypy-extensions = ">=0.4.3" 43 | pathspec = ">=0.6,<1" 44 | regex = ">=2020.1.8" 45 | toml = ">=0.10.1" 46 | typed-ast = ">=1.4.0" 47 | typing-extensions = ">=3.7.4" 48 | 49 | [package.extras] 50 | colorama = ["colorama (>=0.4.3)"] 51 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 52 | 53 | [[package]] 54 | name = "click" 55 | version = "7.1.2" 56 | description = "Composable command line interface toolkit" 57 | category = "dev" 58 | optional = false 59 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 60 | 61 | [[package]] 62 | name = "colorama" 63 | version = "0.4.4" 64 | description = "Cross-platform colored terminal text." 65 | category = "main" 66 | optional = false 67 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 68 | 69 | [[package]] 70 | name = "fastapi" 71 | version = "0.62.0" 72 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 73 | category = "main" 74 | optional = false 75 | python-versions = ">=3.6" 76 | 77 | [package.dependencies] 78 | pydantic = ">=1.0.0,<2.0.0" 79 | starlette = "0.13.6" 80 | 81 | [package.extras] 82 | 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 (>=3.0.0,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "email_validator (>=1.1.1,<2.0.0)", "uvicorn (>=0.11.5,<0.12.0)", "async_exit_stack (>=1.0.1,<2.0.0)", "async_generator (>=1.10,<2.0.0)"] 83 | 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 (>=0.11.5,<0.12.0)", "graphene (>=2.1.8,<3.0.0)"] 84 | 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 (>=0.3.0,<0.4.0)", "typer-cli (>=0.0.9,<0.0.10)", "pyyaml (>=5.3.1,<6.0.0)"] 85 | test = ["pytest (==5.4.3)", "pytest-cov (==2.10.0)", "pytest-asyncio (>=0.14.0,<0.15.0)", "mypy (==0.782)", "flake8 (>=3.8.3,<4.0.0)", "black (==19.10b0)", "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,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "databases[sqlite] (>=0.3.2,<0.4.0)", "orjson (>=3.2.1,<4.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)"] 86 | 87 | [[package]] 88 | name = "flake8" 89 | version = "3.8.4" 90 | description = "the modular source code checker: pep8 pyflakes and co" 91 | category = "dev" 92 | optional = false 93 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 94 | 95 | [package.dependencies] 96 | mccabe = ">=0.6.0,<0.7.0" 97 | pycodestyle = ">=2.6.0a1,<2.7.0" 98 | pyflakes = ">=2.2.0,<2.3.0" 99 | 100 | [[package]] 101 | name = "loguru" 102 | version = "0.5.3" 103 | description = "Python logging made (stupidly) simple" 104 | category = "main" 105 | optional = false 106 | python-versions = ">=3.5" 107 | 108 | [package.dependencies] 109 | colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} 110 | win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} 111 | 112 | [package.extras] 113 | dev = ["codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "tox (>=3.9.0)", "tox-travis (>=0.12)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "Sphinx (>=2.2.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "black (>=19.10b0)", "isort (>=5.1.1)"] 114 | 115 | [[package]] 116 | name = "mccabe" 117 | version = "0.6.1" 118 | description = "McCabe checker, plugin for flake8" 119 | category = "dev" 120 | optional = false 121 | python-versions = "*" 122 | 123 | [[package]] 124 | name = "more-itertools" 125 | version = "8.6.0" 126 | description = "More routines for operating on iterables, beyond itertools" 127 | category = "dev" 128 | optional = false 129 | python-versions = ">=3.5" 130 | 131 | [[package]] 132 | name = "mypy-extensions" 133 | version = "0.4.3" 134 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 135 | category = "dev" 136 | optional = false 137 | python-versions = "*" 138 | 139 | [[package]] 140 | name = "packaging" 141 | version = "20.8" 142 | description = "Core utilities for Python packages" 143 | category = "dev" 144 | optional = false 145 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 146 | 147 | [package.dependencies] 148 | pyparsing = ">=2.0.2" 149 | 150 | [[package]] 151 | name = "pathspec" 152 | version = "0.8.1" 153 | description = "Utility library for gitignore style pattern matching of file paths." 154 | category = "dev" 155 | optional = false 156 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 157 | 158 | [[package]] 159 | name = "pluggy" 160 | version = "0.13.1" 161 | description = "plugin and hook calling mechanisms for python" 162 | category = "dev" 163 | optional = false 164 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 165 | 166 | [package.extras] 167 | dev = ["pre-commit", "tox"] 168 | 169 | [[package]] 170 | name = "py" 171 | version = "1.10.0" 172 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 173 | category = "dev" 174 | optional = false 175 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 176 | 177 | [[package]] 178 | name = "pycodestyle" 179 | version = "2.6.0" 180 | description = "Python style guide checker" 181 | category = "dev" 182 | optional = false 183 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 184 | 185 | [[package]] 186 | name = "pydantic" 187 | version = "1.7.3" 188 | description = "Data validation and settings management using python 3.6 type hinting" 189 | category = "main" 190 | optional = false 191 | python-versions = ">=3.6" 192 | 193 | [package.extras] 194 | dotenv = ["python-dotenv (>=0.10.4)"] 195 | email = ["email-validator (>=1.0.3)"] 196 | typing_extensions = ["typing-extensions (>=3.7.2)"] 197 | 198 | [[package]] 199 | name = "pyflakes" 200 | version = "2.2.0" 201 | description = "passive checker of Python programs" 202 | category = "dev" 203 | optional = false 204 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 205 | 206 | [[package]] 207 | name = "pyparsing" 208 | version = "2.4.7" 209 | description = "Python parsing module" 210 | category = "dev" 211 | optional = false 212 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 213 | 214 | [[package]] 215 | name = "pytest" 216 | version = "5.4.3" 217 | description = "pytest: simple powerful testing with Python" 218 | category = "dev" 219 | optional = false 220 | python-versions = ">=3.5" 221 | 222 | [package.dependencies] 223 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 224 | attrs = ">=17.4.0" 225 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 226 | more-itertools = ">=4.0.0" 227 | packaging = "*" 228 | pluggy = ">=0.12,<1.0" 229 | py = ">=1.5.0" 230 | wcwidth = "*" 231 | 232 | [package.extras] 233 | checkqa-mypy = ["mypy (==v0.761)"] 234 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 235 | 236 | [[package]] 237 | name = "regex" 238 | version = "2020.11.13" 239 | description = "Alternative regular expression module, to replace re." 240 | category = "dev" 241 | optional = false 242 | python-versions = "*" 243 | 244 | [[package]] 245 | name = "sqlalchemy" 246 | version = "1.3.20" 247 | description = "Database Abstraction Library" 248 | category = "main" 249 | optional = false 250 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 251 | 252 | [package.extras] 253 | mssql = ["pyodbc"] 254 | mssql_pymssql = ["pymssql"] 255 | mssql_pyodbc = ["pyodbc"] 256 | mysql = ["mysqlclient"] 257 | oracle = ["cx-oracle"] 258 | postgresql = ["psycopg2"] 259 | postgresql_pg8000 = ["pg8000"] 260 | postgresql_psycopg2binary = ["psycopg2-binary"] 261 | postgresql_psycopg2cffi = ["psycopg2cffi"] 262 | pymysql = ["pymysql"] 263 | 264 | [[package]] 265 | name = "starlette" 266 | version = "0.13.6" 267 | description = "The little ASGI library that shines." 268 | category = "main" 269 | optional = false 270 | python-versions = ">=3.6" 271 | 272 | [package.extras] 273 | full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] 274 | 275 | [[package]] 276 | name = "toml" 277 | version = "0.10.2" 278 | description = "Python Library for Tom's Obvious, Minimal Language" 279 | category = "dev" 280 | optional = false 281 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 282 | 283 | [[package]] 284 | name = "typed-ast" 285 | version = "1.4.1" 286 | description = "a fork of Python 2 and 3 ast modules with type comment support" 287 | category = "dev" 288 | optional = false 289 | python-versions = "*" 290 | 291 | [[package]] 292 | name = "typing-extensions" 293 | version = "3.7.4.3" 294 | description = "Backported and Experimental Type Hints for Python 3.5+" 295 | category = "dev" 296 | optional = false 297 | python-versions = "*" 298 | 299 | [[package]] 300 | name = "wcwidth" 301 | version = "0.2.5" 302 | description = "Measures the displayed width of unicode strings in a terminal" 303 | category = "dev" 304 | optional = false 305 | python-versions = "*" 306 | 307 | [[package]] 308 | name = "win32-setctime" 309 | version = "1.0.3" 310 | description = "A small Python utility to set file creation time on Windows" 311 | category = "main" 312 | optional = false 313 | python-versions = ">=3.5" 314 | 315 | [package.extras] 316 | dev = ["pytest (>=4.6.2)", "black (>=19.3b0)"] 317 | 318 | [metadata] 319 | lock-version = "1.1" 320 | python-versions = "^3.8" 321 | content-hash = "00d53d7e3dff512c30ef26e8d14a5bcd277dfcbfb92cb8aaab7490f2bfc7e541" 322 | 323 | [metadata.files] 324 | appdirs = [ 325 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 326 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 327 | ] 328 | atomicwrites = [ 329 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 330 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 331 | ] 332 | attrs = [ 333 | {file = "attrs-20.3.0-py2.py3-none-any.whl", hash = "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6"}, 334 | {file = "attrs-20.3.0.tar.gz", hash = "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"}, 335 | ] 336 | black = [ 337 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 338 | ] 339 | click = [ 340 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 341 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 342 | ] 343 | colorama = [ 344 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 345 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 346 | ] 347 | fastapi = [ 348 | {file = "fastapi-0.62.0-py3-none-any.whl", hash = "sha256:62074dd38541d9d7245f3aacbbd0d44340c53d56186c9b249d261a18dad4874b"}, 349 | {file = "fastapi-0.62.0.tar.gz", hash = "sha256:8f4c64cd9cea67fb7dd175ca5015961efa572b9f43a8731014dac8929d86225f"}, 350 | ] 351 | flake8 = [ 352 | {file = "flake8-3.8.4-py2.py3-none-any.whl", hash = "sha256:749dbbd6bfd0cf1318af27bf97a14e28e5ff548ef8e5b1566ccfb25a11e7c839"}, 353 | {file = "flake8-3.8.4.tar.gz", hash = "sha256:aadae8761ec651813c24be05c6f7b4680857ef6afaae4651a4eccaef97ce6c3b"}, 354 | ] 355 | loguru = [ 356 | {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"}, 357 | {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"}, 358 | ] 359 | mccabe = [ 360 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 361 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 362 | ] 363 | more-itertools = [ 364 | {file = "more-itertools-8.6.0.tar.gz", hash = "sha256:b3a9005928e5bed54076e6e549c792b306fddfe72b2d1d22dd63d42d5d3899cf"}, 365 | {file = "more_itertools-8.6.0-py3-none-any.whl", hash = "sha256:8e1a2a43b2f2727425f2b5839587ae37093f19153dc26c0927d1048ff6557330"}, 366 | ] 367 | mypy-extensions = [ 368 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 369 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 370 | ] 371 | packaging = [ 372 | {file = "packaging-20.8-py2.py3-none-any.whl", hash = "sha256:24e0da08660a87484d1602c30bb4902d74816b6985b93de36926f5bc95741858"}, 373 | {file = "packaging-20.8.tar.gz", hash = "sha256:78598185a7008a470d64526a8059de9aaa449238f280fc9eb6b13ba6c4109093"}, 374 | ] 375 | pathspec = [ 376 | {file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"}, 377 | {file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"}, 378 | ] 379 | pluggy = [ 380 | {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, 381 | {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, 382 | ] 383 | py = [ 384 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 385 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 386 | ] 387 | pycodestyle = [ 388 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 389 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 390 | ] 391 | pydantic = [ 392 | {file = "pydantic-1.7.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c59ea046aea25be14dc22d69c97bee629e6d48d2b2ecb724d7fe8806bf5f61cd"}, 393 | {file = "pydantic-1.7.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a4143c8d0c456a093387b96e0f5ee941a950992904d88bc816b4f0e72c9a0009"}, 394 | {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:d8df4b9090b595511906fa48deda47af04e7d092318bfb291f4d45dfb6bb2127"}, 395 | {file = "pydantic-1.7.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:514b473d264671a5c672dfb28bdfe1bf1afd390f6b206aa2ec9fed7fc592c48e"}, 396 | {file = "pydantic-1.7.3-cp36-cp36m-win_amd64.whl", hash = "sha256:dba5c1f0a3aeea5083e75db9660935da90216f8a81b6d68e67f54e135ed5eb23"}, 397 | {file = "pydantic-1.7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59e45f3b694b05a69032a0d603c32d453a23f0de80844fb14d55ab0c6c78ff2f"}, 398 | {file = "pydantic-1.7.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:5b24e8a572e4b4c18f614004dda8c9f2c07328cb5b6e314d6e1bbd536cb1a6c1"}, 399 | {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:b2b054d095b6431cdda2f852a6d2f0fdec77686b305c57961b4c5dd6d863bf3c"}, 400 | {file = "pydantic-1.7.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:025bf13ce27990acc059d0c5be46f416fc9b293f45363b3d19855165fee1874f"}, 401 | {file = "pydantic-1.7.3-cp37-cp37m-win_amd64.whl", hash = "sha256:6e3874aa7e8babd37b40c4504e3a94cc2023696ced5a0500949f3347664ff8e2"}, 402 | {file = "pydantic-1.7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e682f6442ebe4e50cb5e1cfde7dda6766fb586631c3e5569f6aa1951fd1a76ef"}, 403 | {file = "pydantic-1.7.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:185e18134bec5ef43351149fe34fda4758e53d05bb8ea4d5928f0720997b79ef"}, 404 | {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:f5b06f5099e163295b8ff5b1b71132ecf5866cc6e7f586d78d7d3fd6e8084608"}, 405 | {file = "pydantic-1.7.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:24ca47365be2a5a3cc3f4a26dcc755bcdc9f0036f55dcedbd55663662ba145ec"}, 406 | {file = "pydantic-1.7.3-cp38-cp38-win_amd64.whl", hash = "sha256:d1fe3f0df8ac0f3a9792666c69a7cd70530f329036426d06b4f899c025aca74e"}, 407 | {file = "pydantic-1.7.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f6864844b039805add62ebe8a8c676286340ba0c6d043ae5dea24114b82a319e"}, 408 | {file = "pydantic-1.7.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:ecb54491f98544c12c66ff3d15e701612fc388161fd455242447083350904730"}, 409 | {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:ffd180ebd5dd2a9ac0da4e8b995c9c99e7c74c31f985ba090ee01d681b1c4b95"}, 410 | {file = "pydantic-1.7.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8d72e814c7821125b16f1553124d12faba88e85405b0864328899aceaad7282b"}, 411 | {file = "pydantic-1.7.3-cp39-cp39-win_amd64.whl", hash = "sha256:475f2fa134cf272d6631072554f845d0630907fce053926ff634cc6bc45bf1af"}, 412 | {file = "pydantic-1.7.3-py3-none-any.whl", hash = "sha256:38be427ea01a78206bcaf9a56f835784afcba9e5b88fbdce33bbbfbcd7841229"}, 413 | {file = "pydantic-1.7.3.tar.gz", hash = "sha256:213125b7e9e64713d16d988d10997dabc6a1f73f3991e1ff8e35ebb1409c7dc9"}, 414 | ] 415 | pyflakes = [ 416 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 417 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 418 | ] 419 | pyparsing = [ 420 | {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, 421 | {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, 422 | ] 423 | pytest = [ 424 | {file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"}, 425 | {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, 426 | ] 427 | regex = [ 428 | {file = "regex-2020.11.13-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:8b882a78c320478b12ff024e81dc7d43c1462aa4a3341c754ee65d857a521f85"}, 429 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a63f1a07932c9686d2d416fb295ec2c01ab246e89b4d58e5fa468089cab44b70"}, 430 | {file = "regex-2020.11.13-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:6e4b08c6f8daca7d8f07c8d24e4331ae7953333dbd09c648ed6ebd24db5a10ee"}, 431 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:bba349276b126947b014e50ab3316c027cac1495992f10e5682dc677b3dfa0c5"}, 432 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:56e01daca75eae420bce184edd8bb341c8eebb19dd3bce7266332258f9fb9dd7"}, 433 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:6a8ce43923c518c24a2579fda49f093f1397dad5d18346211e46f134fc624e31"}, 434 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:1ab79fcb02b930de09c76d024d279686ec5d532eb814fd0ed1e0051eb8bd2daa"}, 435 | {file = "regex-2020.11.13-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9801c4c1d9ae6a70aeb2128e5b4b68c45d4f0af0d1535500884d644fa9b768c6"}, 436 | {file = "regex-2020.11.13-cp36-cp36m-win32.whl", hash = "sha256:49cae022fa13f09be91b2c880e58e14b6da5d10639ed45ca69b85faf039f7a4e"}, 437 | {file = "regex-2020.11.13-cp36-cp36m-win_amd64.whl", hash = "sha256:749078d1eb89484db5f34b4012092ad14b327944ee7f1c4f74d6279a6e4d1884"}, 438 | {file = "regex-2020.11.13-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b2f4007bff007c96a173e24dcda236e5e83bde4358a557f9ccf5e014439eae4b"}, 439 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:38c8fd190db64f513fe4e1baa59fed086ae71fa45083b6936b52d34df8f86a88"}, 440 | {file = "regex-2020.11.13-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5862975b45d451b6db51c2e654990c1820523a5b07100fc6903e9c86575202a0"}, 441 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:262c6825b309e6485ec2493ffc7e62a13cf13fb2a8b6d212f72bd53ad34118f1"}, 442 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:bafb01b4688833e099d79e7efd23f99172f501a15c44f21ea2118681473fdba0"}, 443 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:e32f5f3d1b1c663af7f9c4c1e72e6ffe9a78c03a31e149259f531e0fed826512"}, 444 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:3bddc701bdd1efa0d5264d2649588cbfda549b2899dc8d50417e47a82e1387ba"}, 445 | {file = "regex-2020.11.13-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:02951b7dacb123d8ea6da44fe45ddd084aa6777d4b2454fa0da61d569c6fa538"}, 446 | {file = "regex-2020.11.13-cp37-cp37m-win32.whl", hash = "sha256:0d08e71e70c0237883d0bef12cad5145b84c3705e9c6a588b2a9c7080e5af2a4"}, 447 | {file = "regex-2020.11.13-cp37-cp37m-win_amd64.whl", hash = "sha256:1fa7ee9c2a0e30405e21031d07d7ba8617bc590d391adfc2b7f1e8b99f46f444"}, 448 | {file = "regex-2020.11.13-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:baf378ba6151f6e272824b86a774326f692bc2ef4cc5ce8d5bc76e38c813a55f"}, 449 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e3faaf10a0d1e8e23a9b51d1900b72e1635c2d5b0e1bea1c18022486a8e2e52d"}, 450 | {file = "regex-2020.11.13-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2a11a3e90bd9901d70a5b31d7dd85114755a581a5da3fc996abfefa48aee78af"}, 451 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d1ebb090a426db66dd80df8ca85adc4abfcbad8a7c2e9a5ec7513ede522e0a8f"}, 452 | {file = "regex-2020.11.13-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:b2b1a5ddae3677d89b686e5c625fc5547c6e492bd755b520de5332773a8af06b"}, 453 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:2c99e97d388cd0a8d30f7c514d67887d8021541b875baf09791a3baad48bb4f8"}, 454 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:c084582d4215593f2f1d28b65d2a2f3aceff8342aa85afd7be23a9cad74a0de5"}, 455 | {file = "regex-2020.11.13-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:a3d748383762e56337c39ab35c6ed4deb88df5326f97a38946ddd19028ecce6b"}, 456 | {file = "regex-2020.11.13-cp38-cp38-win32.whl", hash = "sha256:7913bd25f4ab274ba37bc97ad0e21c31004224ccb02765ad984eef43e04acc6c"}, 457 | {file = "regex-2020.11.13-cp38-cp38-win_amd64.whl", hash = "sha256:6c54ce4b5d61a7129bad5c5dc279e222afd00e721bf92f9ef09e4fae28755683"}, 458 | {file = "regex-2020.11.13-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1862a9d9194fae76a7aaf0150d5f2a8ec1da89e8b55890b1786b8f88a0f619dc"}, 459 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4902e6aa086cbb224241adbc2f06235927d5cdacffb2425c73e6570e8d862364"}, 460 | {file = "regex-2020.11.13-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:7a25fcbeae08f96a754b45bdc050e1fb94b95cab046bf56b016c25e9ab127b3e"}, 461 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:d2d8ce12b7c12c87e41123997ebaf1a5767a5be3ec545f64675388970f415e2e"}, 462 | {file = "regex-2020.11.13-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f7d29a6fc4760300f86ae329e3b6ca28ea9c20823df123a2ea8693e967b29917"}, 463 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:717881211f46de3ab130b58ec0908267961fadc06e44f974466d1887f865bd5b"}, 464 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:3128e30d83f2e70b0bed9b2a34e92707d0877e460b402faca908c6667092ada9"}, 465 | {file = "regex-2020.11.13-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:8f6a2229e8ad946e36815f2a03386bb8353d4bde368fdf8ca5f0cb97264d3b5c"}, 466 | {file = "regex-2020.11.13-cp39-cp39-win32.whl", hash = "sha256:f8f295db00ef5f8bae530fc39af0b40486ca6068733fb860b42115052206466f"}, 467 | {file = "regex-2020.11.13-cp39-cp39-win_amd64.whl", hash = "sha256:a15f64ae3a027b64496a71ab1f722355e570c3fac5ba2801cafce846bf5af01d"}, 468 | {file = "regex-2020.11.13.tar.gz", hash = "sha256:83d6b356e116ca119db8e7c6fc2983289d87b27b3fac238cfe5dca529d884562"}, 469 | ] 470 | sqlalchemy = [ 471 | {file = "SQLAlchemy-1.3.20-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bad73f9888d30f9e1d57ac8829f8a12091bdee4949b91db279569774a866a18e"}, 472 | {file = "SQLAlchemy-1.3.20-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:e32e3455db14602b6117f0f422f46bc297a3853ae2c322ecd1e2c4c04daf6ed5"}, 473 | {file = "SQLAlchemy-1.3.20-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5cdfe54c1e37279dc70d92815464b77cd8ee30725adc9350f06074f91dbfeed2"}, 474 | {file = "SQLAlchemy-1.3.20-cp27-cp27m-win32.whl", hash = "sha256:2e9bd5b23bba8ae8ce4219c9333974ff5e103c857d9ff0e4b73dc4cb244c7d86"}, 475 | {file = "SQLAlchemy-1.3.20-cp27-cp27m-win_amd64.whl", hash = "sha256:5d92c18458a4aa27497a986038d5d797b5279268a2de303cd00910658e8d149c"}, 476 | {file = "SQLAlchemy-1.3.20-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:53fd857c6c8ffc0aa6a5a3a2619f6a74247e42ec9e46b836a8ffa4abe7aab327"}, 477 | {file = "SQLAlchemy-1.3.20-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:0a92745bb1ebbcb3985ed7bda379b94627f0edbc6c82e9e4bac4fb5647ae609a"}, 478 | {file = "SQLAlchemy-1.3.20-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:b6f036ecc017ec2e2cc2a40615b41850dc7aaaea6a932628c0afc73ab98ba3fb"}, 479 | {file = "SQLAlchemy-1.3.20-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3aa6d45e149a16aa1f0c46816397e12313d5e37f22205c26e06975e150ffcf2a"}, 480 | {file = "SQLAlchemy-1.3.20-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:ed53209b5f0f383acb49a927179fa51a6e2259878e164273ebc6815f3a752465"}, 481 | {file = "SQLAlchemy-1.3.20-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:d3b709d64b5cf064972b3763b47139e4a0dc4ae28a36437757f7663f67b99710"}, 482 | {file = "SQLAlchemy-1.3.20-cp35-cp35m-win32.whl", hash = "sha256:950f0e17ffba7a7ceb0dd056567bc5ade22a11a75920b0e8298865dc28c0eff6"}, 483 | {file = "SQLAlchemy-1.3.20-cp35-cp35m-win_amd64.whl", hash = "sha256:8dcbf377529a9af167cbfc5b8acec0fadd7c2357fc282a1494c222d3abfc9629"}, 484 | {file = "SQLAlchemy-1.3.20-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0157c269701d88f5faf1fa0e4560e4d814f210c01a5b55df3cab95e9346a8bcc"}, 485 | {file = "SQLAlchemy-1.3.20-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7cd40cb4bc50d9e87b3540b23df6e6b24821ba7e1f305c1492b0806c33dbdbec"}, 486 | {file = "SQLAlchemy-1.3.20-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:c092fe282de83d48e64d306b4bce03114859cdbfe19bf8a978a78a0d44ddadb1"}, 487 | {file = "SQLAlchemy-1.3.20-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:166917a729b9226decff29416f212c516227c2eb8a9c9f920d69ced24e30109f"}, 488 | {file = "SQLAlchemy-1.3.20-cp36-cp36m-win32.whl", hash = "sha256:632b32183c0cb0053194a4085c304bc2320e5299f77e3024556fa2aa395c2a8b"}, 489 | {file = "SQLAlchemy-1.3.20-cp36-cp36m-win_amd64.whl", hash = "sha256:bbc58fca72ce45a64bb02b87f73df58e29848b693869e58bd890b2ddbb42d83b"}, 490 | {file = "SQLAlchemy-1.3.20-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:b15002b9788ffe84e42baffc334739d3b68008a973d65fad0a410ca5d0531980"}, 491 | {file = "SQLAlchemy-1.3.20-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:9e379674728f43a0cd95c423ac0e95262500f9bfd81d33b999daa8ea1756d162"}, 492 | {file = "SQLAlchemy-1.3.20-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:2b5dafed97f778e9901b79cc01b88d39c605e0545b4541f2551a2fd785adc15b"}, 493 | {file = "SQLAlchemy-1.3.20-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:fcdb3755a7c355bc29df1b5e6fb8226d5c8b90551d202d69d0076a8a5649d68b"}, 494 | {file = "SQLAlchemy-1.3.20-cp37-cp37m-win32.whl", hash = "sha256:bca4d367a725694dae3dfdc86cf1d1622b9f414e70bd19651f5ac4fb3aa96d61"}, 495 | {file = "SQLAlchemy-1.3.20-cp37-cp37m-win_amd64.whl", hash = "sha256:f605f348f4e6a2ba00acb3399c71d213b92f27f2383fc4abebf7a37368c12142"}, 496 | {file = "SQLAlchemy-1.3.20-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:84f0ac4a09971536b38cc5d515d6add7926a7e13baa25135a1dbb6afa351a376"}, 497 | {file = "SQLAlchemy-1.3.20-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2909dffe5c9a615b7e6c92d1ac2d31e3026dc436440a4f750f4749d114d88ceb"}, 498 | {file = "SQLAlchemy-1.3.20-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c3ab23ee9674336654bf9cac30eb75ac6acb9150dc4b1391bec533a7a4126471"}, 499 | {file = "SQLAlchemy-1.3.20-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:009e8388d4d551a2107632921320886650b46332f61dc935e70c8bcf37d8e0d6"}, 500 | {file = "SQLAlchemy-1.3.20-cp38-cp38-win32.whl", hash = "sha256:bf53d8dddfc3e53a5bda65f7f4aa40fae306843641e3e8e701c18a5609471edf"}, 501 | {file = "SQLAlchemy-1.3.20-cp38-cp38-win_amd64.whl", hash = "sha256:7c735c7a6db8ee9554a3935e741cf288f7dcbe8706320251eb38c412e6a4281d"}, 502 | {file = "SQLAlchemy-1.3.20-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4bdbdb8ca577c6c366d15791747c1de6ab14529115a2eb52774240c412a7b403"}, 503 | {file = "SQLAlchemy-1.3.20-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:ce64a44c867d128ab8e675f587aae7f61bd2db836a3c4ba522d884cd7c298a77"}, 504 | {file = "SQLAlchemy-1.3.20-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:be41d5de7a8e241864189b7530ca4aaf56a5204332caa70555c2d96379e18079"}, 505 | {file = "SQLAlchemy-1.3.20-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:1f5f369202912be72fdf9a8f25067a5ece31a2b38507bb869306f173336348da"}, 506 | {file = "SQLAlchemy-1.3.20-cp39-cp39-win32.whl", hash = "sha256:0cca1844ba870e81c03633a99aa3dc62256fb96323431a5dec7d4e503c26372d"}, 507 | {file = "SQLAlchemy-1.3.20-cp39-cp39-win_amd64.whl", hash = "sha256:d05cef4a164b44ffda58200efcb22355350979e000828479971ebca49b82ddb1"}, 508 | {file = "SQLAlchemy-1.3.20.tar.gz", hash = "sha256:d2f25c7f410338d31666d7ddedfa67570900e248b940d186b48461bd4e5569a1"}, 509 | ] 510 | starlette = [ 511 | {file = "starlette-0.13.6-py3-none-any.whl", hash = "sha256:bd2ffe5e37fb75d014728511f8e68ebf2c80b0fa3d04ca1479f4dc752ae31ac9"}, 512 | {file = "starlette-0.13.6.tar.gz", hash = "sha256:ebe8ee08d9be96a3c9f31b2cb2a24dbdf845247b745664bd8a3f9bd0c977fdbc"}, 513 | ] 514 | toml = [ 515 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 516 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 517 | ] 518 | typed-ast = [ 519 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 520 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 521 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 522 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 523 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 524 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 525 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 526 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:fcf135e17cc74dbfbc05894ebca928ffeb23d9790b3167a674921db19082401f"}, 527 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 528 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 529 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 530 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 531 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 532 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:f208eb7aff048f6bea9586e61af041ddf7f9ade7caed625742af423f6bae3298"}, 533 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 534 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 535 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 536 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 537 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 538 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:7e4c9d7658aaa1fc80018593abdf8598bf91325af6af5cce4ce7c73bc45ea53d"}, 539 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 540 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 541 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 542 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92c325624e304ebf0e025d1224b77dd4e6393f18aab8d829b5b7e04afe9b7a2c"}, 543 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:d648b8e3bf2fe648745c8ffcee3db3ff903d0817a01a12dd6a6ea7a8f4889072"}, 544 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:fac11badff8313e23717f3dada86a15389d0708275bddf766cca67a84ead3e91"}, 545 | {file = "typed_ast-1.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:0d8110d78a5736e16e26213114a38ca35cb15b6515d535413b090bd50951556d"}, 546 | {file = "typed_ast-1.4.1-cp39-cp39-win32.whl", hash = "sha256:b52ccf7cfe4ce2a1064b18594381bccf4179c2ecf7f513134ec2f993dd4ab395"}, 547 | {file = "typed_ast-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:3742b32cf1c6ef124d57f95be609c473d7ec4c14d0090e5a5e05a15269fb4d0c"}, 548 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 549 | ] 550 | typing-extensions = [ 551 | {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, 552 | {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, 553 | {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, 554 | ] 555 | wcwidth = [ 556 | {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, 557 | {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, 558 | ] 559 | win32-setctime = [ 560 | {file = "win32_setctime-1.0.3-py3-none-any.whl", hash = "sha256:dc925662de0a6eb987f0b01f599c01a8236cb8c62831c22d9cada09ad958243e"}, 561 | {file = "win32_setctime-1.0.3.tar.gz", hash = "sha256:4e88556c32fdf47f64165a2180ba4552f8bb32c1103a2fafd05723a0bd42bd4b"}, 562 | ] 563 | --------------------------------------------------------------------------------