├── 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 |
--------------------------------------------------------------------------------