├── .dist.env ├── .dockerignore ├── .flake8 ├── .gitignore ├── .pre-commit-config.yaml ├── .tool-versions ├── Dockerfile ├── Dockerfile.worker ├── LICENSE ├── README.md ├── alembic.ini ├── app ├── __init__.py ├── application.py ├── main.py ├── models │ ├── __init__.py │ ├── orm │ │ ├── __init__.py │ │ ├── base.py │ │ ├── migrations │ │ │ ├── README │ │ │ ├── env.py │ │ │ └── script.py.mako │ │ ├── queries │ │ │ └── __init__.py │ │ └── user.py │ └── pydantic │ │ ├── __init__.py │ │ ├── base.py │ │ ├── database.py │ │ └── user.py ├── routes │ ├── __init__.py │ └── users.py ├── settings │ ├── __init__.py │ ├── arq.py │ ├── globals.py │ └── prestart.sh ├── tasks │ ├── __init__.py │ └── messaging.py └── worker.py ├── docker-compose.local.yml ├── docker-compose.worker.yml ├── docker-compose.yml ├── poetry.lock └── pyproject.toml /.dist.env: -------------------------------------------------------------------------------- 1 | 2 | ### Application Variables 3 | 4 | # TO ADD APPLICATION GLOBALS: 5 | # 1. Duplicate .dist.env, rename to .env 6 | # 2. Add variable to the list below. 7 | # 3. Add variable to app/settings/globals.py, define defaults, etc. 8 | 9 | DATABASE=fill 10 | DB_USER=fill # Optional, remove if unnecessary 11 | DB_PASSWORD=fill # Optional, remove if unnecessary 12 | DB_HOST=fill # Optional, remove if unnecessary 13 | DB_PORT=fill # Optional, remove if unnecessary 14 | 15 | REDIS_IP=fill 16 | REDIS_PORT=fill 17 | 18 | SENTRY_DSN= 19 | 20 | # NOTE: Separate function references by comma. Example: 21 | # ARQ_BACKGROUND_FUNCTIONS=app.path.example_function, app.tasks.example.other_function 22 | ARQ_BACKGROUND_FUNCTIONS=app.tasks.messaging.send_message 23 | 24 | ### Docker Runtime Variables 25 | # All available options at https://github.com/tiangolo/uvicorn-gunicorn-docker 26 | # Includes custom gunicorn, concurrency, workers and logging settings 27 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # IDE Fragments 2 | /.vscode 3 | *__pycache__* 4 | *.idea* 5 | 6 | # Docker Files 7 | docker-compose.yml 8 | 9 | # Ignore Files 10 | .gitignore 11 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203, E266, E501, W503, F403, F401 3 | max-line-length = 79 4 | max-complexity = 18 5 | select = B,C,E,F,W,T4,B9 6 | per-file-ignores = 7 | app/models/orm/migrations/env.py:E402 8 | app/main.py:E402 9 | app/worker.py:E402 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE Fragments 2 | /.vscode 3 | *__pycache__* 4 | *.idea* 5 | 6 | # Environment Files 7 | /.env 8 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/asottile/seed-isort-config 3 | rev: v2.1.1 4 | hooks: 5 | - id: seed-isort-config 6 | args: [--application-directories, "./app:./"] 7 | - repo: https://github.com/pre-commit/mirrors-isort 8 | rev: v4.3.21 9 | hooks: 10 | - id: isort 11 | additional_dependencies: ["toml"] 12 | - repo: https://github.com/ambv/black 13 | rev: stable 14 | hooks: 15 | - id: black 16 | - repo: https://gitlab.com/pycqa/flake8 17 | rev: 3.7.9 18 | hooks: 19 | - id: flake8 20 | additional_dependencies: [ 21 | "git+https://github.com/PyCQA/pyflakes#egg=pyflakes", 22 | "git+https://gitlab.com/PyCQA/pycodestyle#egg=pycodestyle", 23 | ] 24 | default_language_version: 25 | python: python3.8 -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.8.2 2.7.16 2 | poetry 1.0.5 -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn:python3.8-alpine3.10 2 | 3 | RUN apk update && apk add gcc libffi-dev g++ postgresql-dev make curl 4 | 5 | RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python 6 | 7 | COPY pyproject.toml pyproject.toml 8 | COPY poetry.lock poetry.lock 9 | 10 | RUN source $HOME/.poetry/env && poetry config virtualenvs.create false && poetry install --no-dev --no-ansi --no-root 11 | 12 | RUN apk del libffi-dev g++ make curl 13 | 14 | COPY ./app /app/app 15 | 16 | COPY ./alembic.ini /app/alembic.ini 17 | 18 | COPY ./app/settings/prestart.sh /app/prestart.sh 19 | -------------------------------------------------------------------------------- /Dockerfile.worker: -------------------------------------------------------------------------------- 1 | FROM python:3.8-alpine 2 | 3 | ENV PYTHONFAULTHANDLER=1 \ 4 | PYTHONUNBUFFERED=1 \ 5 | PYTHONHASHSEED=random \ 6 | PYTHONDONTWRITEBYTECODE=1 7 | 8 | RUN apk update && apk add --no-cache gcc libffi-dev openssl-dev g++ postgresql-dev make curl 9 | 10 | RUN curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python 11 | 12 | COPY pyproject.toml pyproject.toml 13 | COPY poetry.lock poetry.lock 14 | 15 | RUN source $HOME/.poetry/env && poetry config virtualenvs.create false && poetry install --no-dev --no-ansi --no-root 16 | 17 | RUN apk del libffi-dev g++ make curl 18 | 19 | COPY ./app /app 20 | 21 | CMD ["arq", "app.worker.WorkerSettings"] 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Leo Sussan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # fastapi-gino-arq-uvicorn 2 | High-performance Async REST API, in Python. FastAPI + GINO + Arq + Uvicorn (powered by Redis & PostgreSQL). 3 | 4 | ## Contents 5 | - [fastapi-gino-arq-uvicorn](#fastapi-gino-arq-uvicorn) 6 | - [Contents](#contents) 7 | - [Get Started](#get-started) 8 | - [Setup](#setup) 9 | - [Run](#run) 10 | - [Run Locally](#run-locally) 11 | - [Run Locally with Docker-Compose.](#run-locally-with-docker-compose) 12 | - [Build Your Application](#build-your-application) 13 | - [Features](#features) 14 | - [Core Dependencies](#core-dependencies) 15 | - [Additional Dependencies](#additional-dependencies) 16 | 17 | ## Get Started 18 | ### Setup 19 | 1. Clone this Repository. `git clone https://github.com/leosussan/fastapi-gino-arq-uvicorn.git` 20 | 2. Install `Python 3.8` and `poetry`. 21 | * Recommended Method: `asdf` - a universal version manager (think `nvm` or `pyenv`) 22 | * Follow [these instructions](https://asdf-vm.com/#/core-manage-asdf-vm?id=install-asdf-vm) to install `asdf`. 23 | * Run the following commands from the project root: 24 | * `asdf plugin add python` 25 | * `asdf plugin add poetry` 26 | * `asdf install` -- will download & configure this project's `Python` + `poetry` setup 27 | * _~NOTE_: your machine must have a system version of Python installed. If you don't, run the following: `asdf install python 3.8.2 && asdf global python 3.8.2` 28 | * If you have `Python 3.8` and `poetry` installed already, please feel free to skip. 29 | 3. Install dependencies (`poetry install`). 30 | 4. Activate pre-commit hooks (in `poetry shell`, run `pre-commit install`). 31 | 5. Make a copy of `.dist.env`, rename to `.env`. Fill in PostgreSQL, Redis, Sentry (optional) variables. 32 | 6. Generate DB Migrations: in `poetry shell`, run `alembic revision --autogenerate`. 33 | * Apply migrations manually with `alembic upgrade head`. 34 | * If using the Dockerfile, migrations are applied at startup. 35 | 36 | ### Run 37 | 38 | #### Run Locally 39 | _NOTE: You must have PostgreSQL & Redis running locally._ 40 | 41 | 1. Make sure PostgreSQL & Redis are running locally. 42 | 2. Run: 43 | - FastAPI Application: 44 | * _For Active Development (w/ auto-reload):_ Run locally with `poetry run task app` 45 | * _For Debugging (compatible w/ debuggers, no auto-reload):_ Configure debugger to run `python app/main.py`. 46 | - Background Task Worker: 47 | * _For Active Development:_ Run `poetry run task worker` 48 | 49 | #### Run Locally with Docker-Compose. 50 | 1. Make sure `Docker` is running locally. 51 | 2. Run `poetry run task compose-up`*. 52 | - Run `poetry run task compose-down` to spin down, clean up. 53 | 54 | *`app/settings/prestart.sh` will run migrations for you before the app starts. 55 | 56 | ### Build Your Application 57 | * Create routes in `/app/routes`, import & add them to the `ROUTERS` constant in `/app/main.py` 58 | * Create database models to `/app/models/orm`, add them to `/app/models/orm/migrations/env.py` for migrations 59 | * Create pydantic models in `/app/models/pydantic` 60 | * Store complex db queries in `/app/models/orm/queries` 61 | * Store complex tasks in `app/tasks`. 62 | * Add / edit globals to `/.env`, expose & import them from `/app/settings/globals.py` 63 | * Use any coroutine as a background function: store a reference in the `ARQ_BACKGROUND_FUNCTIONS` env. 64 | * Set `SENTRY_DSN` in your environment to enable Sentry. 65 | * Define code to run before launch (migrations, setup, etc) in `/app/settings/prestart.sh` 66 | 67 | ## Features 68 | ### Core Dependencies 69 | * **FastAPI:** touts performance on-par with NodeJS & Go + automatic Swagger + ReDoc generation. 70 | * **GINO:** built on SQLAlchemy core. Lightweight, simple, asynchronous ORM for PostgreSQL. 71 | * **Arq:** Asyncio + Redis = fast, resource-light job queuing & RPC. 72 | * **Uvicorn:** Lightning-fast, asynchronous ASGI server. 73 | * **Optimized Dockerfile:** Optimized Dockerfile for ASGI applications, from https://github.com/tiangolo/uvicorn-gunicorn-docker. 74 | 75 | #### Additional Dependencies 76 | * **PostgreSQL:** Robust, fully-featured, scalable, open-source. 77 | * **Redis:** Fast, simple, broker for the Arq task queue. 78 | * **Pydantic:** Core to FastAPI. Define how data should be in pure, canonical python; validate it with pydantic. 79 | * **Alembic:** Handles database migrations. Compatible with GINO. 80 | * **SQLAlchemy_Utils:** Provides essential handles & datatypes. Compatible with GINO. 81 | * **Sentry:** Open-source, cloud-hosted error + event monitoring. 82 | * **Pre-Commit:** automatic formatting (`black` + `isort`) and linting (`flake8`). 83 | * **Taskipy:** Small, flexible task runner for Poetry. 84 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | # A generic, single database configuration. 2 | 3 | [alembic] 4 | # path to migration scripts 5 | script_location = app/models/orm/migrations 6 | 7 | # template used to generate migration files 8 | # file_template = %%(rev)s_%%(slug)s 9 | 10 | # timezone to use when rendering the date 11 | # within the migration file as well as the filename. 12 | # string value is passed to dateutil.tz.gettz() 13 | # leave blank for localtime 14 | # timezone = 15 | 16 | # max length of characters to apply to the 17 | # "slug" field 18 | # truncate_slug_length = 40 19 | 20 | # set to 'true' to run the environment during 21 | # the 'revision' command, regardless of autogenerate 22 | # revision_environment = false 23 | 24 | # set to 'true' to allow .pyc and .pyo files without 25 | # a source .py file to be detected as revisions in the 26 | # versions/ directory 27 | # sourceless = false 28 | 29 | # version location specification; this defaults 30 | # to app/models/orm/alembic/versions. When using multiple version 31 | # directories, initial revisions must be specified with --version-path 32 | version_locations = app/models/orm/migrations 33 | 34 | # the output encoding used when revision files 35 | # are written from script.py.mako 36 | # output_encoding = utf-8 37 | 38 | # Logging configuration 39 | [loggers] 40 | keys = root,sqlalchemy,alembic 41 | 42 | [handlers] 43 | keys = console 44 | 45 | [formatters] 46 | keys = generic 47 | 48 | [logger_root] 49 | level = WARN 50 | handlers = console 51 | qualname = 52 | 53 | [logger_sqlalchemy] 54 | level = WARN 55 | handlers = 56 | qualname = sqlalchemy.engine 57 | 58 | [logger_alembic] 59 | level = INFO 60 | handlers = 61 | qualname = alembic 62 | 63 | [handler_console] 64 | class = StreamHandler 65 | args = (sys.stderr,) 66 | level = NOTSET 67 | formatter = generic 68 | 69 | [formatter_generic] 70 | format = %(levelname)-5.5s [%(name)s] %(message)s 71 | datefmt = %H:%M:%S 72 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/__init__.py -------------------------------------------------------------------------------- /app/application.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from gino.ext.starlette import Gino 3 | from sentry_sdk import init as initialize_sentry 4 | from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration 5 | from sqlalchemy.schema import MetaData 6 | from starlette.datastructures import Secret 7 | 8 | from .settings.globals import DATABASE_CONFIG, SENTRY_DSN 9 | 10 | if isinstance(SENTRY_DSN, Secret) and SENTRY_DSN.__str__() not in ("None", ""): 11 | initialize_sentry( 12 | dsn=SENTRY_DSN.__str__(), integrations=[SqlalchemyIntegration()] 13 | ) 14 | 15 | app: FastAPI = FastAPI() 16 | db: MetaData = Gino(app, dsn=DATABASE_CONFIG.url) 17 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | # isort:skip_file 2 | 3 | import sys 4 | 5 | sys.path.extend(["./"]) 6 | 7 | from sentry_sdk.integrations.asgi import SentryAsgiMiddleware 8 | from starlette.datastructures import Secret 9 | 10 | from app.application import app 11 | from app.routes.users import router as user_router 12 | from app.settings.globals import SENTRY_DSN 13 | 14 | 15 | ROUTERS = (user_router,) 16 | 17 | for r in ROUTERS: 18 | app.include_router(r) 19 | 20 | if isinstance(SENTRY_DSN, Secret) and SENTRY_DSN.__str__() not in ("None", ""): 21 | app.add_middleware(SentryAsgiMiddleware) 22 | 23 | 24 | if __name__ == "__main__": 25 | import uvicorn 26 | 27 | uvicorn.run(app, host="0.0.0.0", port=8888, log_level="info") 28 | -------------------------------------------------------------------------------- /app/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/models/__init__.py -------------------------------------------------------------------------------- /app/models/orm/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/models/orm/__init__.py -------------------------------------------------------------------------------- /app/models/orm/base.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy.dialects.postgresql import JSONB, UUID 4 | from sqlalchemy_utils import EmailType, generic_repr 5 | 6 | from ...application import db 7 | 8 | db.JSONB, db.UUID, db.EmailType = (JSONB, UUID, EmailType) 9 | 10 | 11 | @generic_repr 12 | class Base(db.Model): 13 | __abstract__ = True 14 | created_on = db.Column( 15 | db.DateTime, default=datetime.utcnow, server_default=db.func.now() 16 | ) 17 | updated_on = db.Column( 18 | db.DateTime, 19 | default=datetime.utcnow, 20 | onupdate=datetime.utcnow, 21 | server_default=db.func.now(), 22 | ) 23 | id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) 24 | -------------------------------------------------------------------------------- /app/models/orm/migrations/README: -------------------------------------------------------------------------------- 1 | Generic single-database configuration. -------------------------------------------------------------------------------- /app/models/orm/migrations/env.py: -------------------------------------------------------------------------------- 1 | # isort:skip_file 2 | 3 | import sys 4 | 5 | sys.path.extend(["./"]) 6 | 7 | from logging.config import fileConfig 8 | 9 | from sqlalchemy import engine_from_config, pool 10 | from alembic import context 11 | 12 | from app.settings.globals import ALEMBIC_CONFIG 13 | 14 | ######################## --- MODELS FOR MIGRATIONS --- ######################## 15 | from app.application import db 16 | from app.models.orm.user import User 17 | 18 | # To include a model in migrations, add a line here. 19 | # from app.models.orm.person import Person 20 | 21 | ############################################################################### 22 | 23 | config = context.config 24 | fileConfig(config.config_file_name) 25 | target_metadata = db 26 | 27 | 28 | def run_migrations_offline(): 29 | """Run migrations in 'offline' mode. 30 | 31 | This configures the context with just a URL 32 | and not an Engine, though an Engine is acceptable 33 | here as well. By skipping the Engine creation 34 | we don't even need a DBAPI to be available. 35 | 36 | Calls to context.execute() here emit the given string to the 37 | script output. 38 | 39 | """ 40 | context.configure( 41 | url=ALEMBIC_CONFIG.url.__to_string__(hide_password=False), 42 | target_metadata=target_metadata, 43 | literal_binds=True, 44 | ) 45 | 46 | with context.begin_transaction(): 47 | context.run_migrations() 48 | 49 | 50 | def run_migrations_online(): 51 | """Run migrations in 'online' mode. 52 | 53 | In this scenario we need to create an Engine 54 | and associate a connection with the context. 55 | 56 | """ 57 | connectable = engine_from_config( 58 | { 59 | "sqlalchemy.url": ALEMBIC_CONFIG.url.__to_string__( 60 | hide_password=False 61 | ) 62 | }, 63 | prefix="sqlalchemy.", 64 | poolclass=pool.NullPool, 65 | ) 66 | with connectable.connect() as connection: 67 | context.configure( 68 | connection=connection, target_metadata=target_metadata 69 | ) 70 | 71 | with context.begin_transaction(): 72 | context.run_migrations() 73 | 74 | 75 | if context.is_offline_mode(): 76 | run_migrations_offline() 77 | else: 78 | run_migrations_online() 79 | -------------------------------------------------------------------------------- /app/models/orm/migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from alembic import op 9 | import sqlalchemy as sa 10 | import sqlalchemy_utils 11 | ${imports if imports else ""} 12 | 13 | # revision identifiers, used by Alembic. 14 | revision = ${repr(up_revision)} 15 | down_revision = ${repr(down_revision)} 16 | branch_labels = ${repr(branch_labels)} 17 | depends_on = ${repr(depends_on)} 18 | 19 | 20 | def upgrade(): 21 | ${upgrades if upgrades else "pass"} 22 | 23 | 24 | def downgrade(): 25 | ${downgrades if downgrades else "pass"} 26 | -------------------------------------------------------------------------------- /app/models/orm/queries/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/models/orm/queries/__init__.py -------------------------------------------------------------------------------- /app/models/orm/user.py: -------------------------------------------------------------------------------- 1 | from .base import Base, db 2 | 3 | 4 | class User(Base): 5 | __tablename__ = "users" 6 | name = db.Column(db.String(255)) 7 | email = db.Column(db.EmailType) 8 | phone_number = db.Column(db.Unicode(20)) 9 | country_code = db.Column(db.Unicode(8)) 10 | -------------------------------------------------------------------------------- /app/models/pydantic/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/models/pydantic/__init__.py -------------------------------------------------------------------------------- /app/models/pydantic/base.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Base(BaseModel): 7 | id: int 8 | created_on: datetime 9 | updated_on: datetime 10 | 11 | class Config: 12 | orm_mode = True 13 | -------------------------------------------------------------------------------- /app/models/pydantic/database.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Optional, Union 2 | 3 | from pydantic import BaseModel, Schema, validator 4 | from pydantic.fields import Field 5 | from sqlalchemy.engine.url import URL 6 | from starlette.datastructures import Secret 7 | 8 | 9 | class DatabaseURL(BaseModel): 10 | drivername: str = Schema( 11 | ..., alias="driver", description="The database driver." 12 | ) 13 | host: str = Schema("localhost", description="Server host.") 14 | port: Optional[Union[str, int]] = Schema( 15 | None, description="Server access port." 16 | ) 17 | username: Optional[str] = Schema( 18 | None, alias="user", description="Username" 19 | ) 20 | password: Optional[Union[str, Secret]] = Schema( 21 | None, description="Password" 22 | ) 23 | database: str = Schema(..., description="Database name.") 24 | url: Optional[URL] = None 25 | 26 | class Config: 27 | arbitrary_types_allowed = True 28 | allow_population_by_alias = True 29 | 30 | @validator("url", always=True) 31 | def build_url(cls, v: Any, field: Field, values: dict): 32 | if isinstance(v, URL): 33 | return v 34 | args = {k: str(v) for k, v in values.items() if v is not None} 35 | return URL(**args) 36 | -------------------------------------------------------------------------------- /app/models/pydantic/user.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, EmailStr 4 | 5 | from .base import Base 6 | 7 | 8 | class User(Base): 9 | name: str 10 | email: EmailStr 11 | phone_number: str 12 | country_code: str 13 | 14 | 15 | class UserCreateIn(BaseModel): 16 | name: str 17 | email: EmailStr 18 | phone_number: str 19 | country_code: str 20 | 21 | 22 | class UserUpdateIn(BaseModel): 23 | name: Optional[str] 24 | email: Optional[EmailStr] 25 | phone_number: Optional[str] 26 | country_code: Optional[str] 27 | -------------------------------------------------------------------------------- /app/routes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/routes/__init__.py -------------------------------------------------------------------------------- /app/routes/users.py: -------------------------------------------------------------------------------- 1 | from arq.connections import ArqRedis, create_pool 2 | from fastapi import APIRouter 3 | 4 | from ..models.orm.user import User as ORMUser 5 | from ..models.pydantic.user import User, UserCreateIn, UserUpdateIn 6 | from ..settings.arq import settings as redis_settings 7 | 8 | router = APIRouter() 9 | 10 | 11 | @router.post("/users", tags=["Users"], response_model=User) 12 | async def create_user(request: UserCreateIn): 13 | new_user: ORMUser = await ORMUser.create(**request.dict()) 14 | redis: ArqRedis = await create_pool(settings=redis_settings) 15 | await redis.enqueue_job( 16 | "send_message", 17 | new_user.id, 18 | "Congratulations! Your account has been created!", 19 | ) 20 | return User.from_orm(new_user) 21 | 22 | 23 | @router.get("/users/{id}", tags=["Users"], response_model=User) 24 | async def retrieve_user(id: int): 25 | user: ORMUser = await ORMUser.get(id) 26 | return User.from_orm(user) 27 | 28 | 29 | @router.put("/users/{id}", tags=["Users"], response_model=User) 30 | async def update_user(request: UserUpdateIn, id: int): 31 | user: ORMUser = await ORMUser.get(id) 32 | updated_fields: User = User.from_orm(request) 33 | await user.update(**updated_fields.dict(skip_defaults=True)).apply() 34 | return User.from_orm(user) 35 | 36 | 37 | @router.delete("/users/{id}", tags=["Users"], response_model=User) 38 | async def delete_user(id: int): 39 | user: ORMUser = await ORMUser.get(id) 40 | return await user.delete() 41 | -------------------------------------------------------------------------------- /app/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/settings/__init__.py -------------------------------------------------------------------------------- /app/settings/arq.py: -------------------------------------------------------------------------------- 1 | from arq.connections import RedisSettings 2 | 3 | from .globals import REDIS_IP, REDIS_PORT 4 | 5 | settings = RedisSettings(host=REDIS_IP, port=REDIS_PORT) 6 | -------------------------------------------------------------------------------- /app/settings/globals.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from typing import Optional 3 | 4 | from starlette.config import Config 5 | from starlette.datastructures import CommaSeparatedStrings, Secret 6 | 7 | from ..models.pydantic.database import DatabaseURL 8 | 9 | p: Path = Path(__file__).parents[2] / ".env" 10 | config: Config = Config(p if p.exists() else None) 11 | 12 | DATABASE: str = config("DATABASE", cast=str) 13 | DB_USER: Optional[str] = config("DB_USER", cast=str, default=None) 14 | DB_PASSWORD: Optional[Secret] = config( 15 | "DB_PASSWORD", cast=Secret, default=None 16 | ) 17 | DB_HOST: str = config("DB_HOST", cast=str, default="localhost") 18 | DB_PORT: int = config("DB_PORT", cast=int, default=5432) 19 | DATABASE_CONFIG: DatabaseURL = DatabaseURL( 20 | drivername="asyncpg", 21 | username=DB_USER, 22 | password=DB_PASSWORD, 23 | host=DB_HOST, 24 | port=DB_PORT, 25 | database=DATABASE, 26 | ) 27 | ALEMBIC_CONFIG: DatabaseURL = DatabaseURL( 28 | drivername="postgresql+psycopg2", 29 | username=DB_USER, 30 | password=DB_PASSWORD, 31 | host=DB_HOST, 32 | port=DB_PORT, 33 | database=DATABASE, 34 | ) 35 | 36 | REDIS_IP: str = config("REDIS_IP", cast=str, default="127.0.0.1") 37 | REDIS_PORT: int = config("REDIS_PORT", cast=int, default=6379) 38 | 39 | SENTRY_DSN: Optional[Secret] = config("SENTRY_DSN", cast=Secret, default=None) 40 | 41 | ARQ_BACKGROUND_FUNCTIONS: Optional[CommaSeparatedStrings] = config( 42 | "ARQ_BACKGROUND_FUNCTIONS", cast=CommaSeparatedStrings, default=None 43 | ) 44 | -------------------------------------------------------------------------------- /app/settings/prestart.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | alembic upgrade head -------------------------------------------------------------------------------- /app/tasks/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leosussan/fastapi-gino-arq-uvicorn/dd4213f4113825ddce3d3a6a39e54ef77df6014f/app/tasks/__init__.py -------------------------------------------------------------------------------- /app/tasks/messaging.py: -------------------------------------------------------------------------------- 1 | from ..application import db 2 | from ..models.orm.user import User as ORMUser 3 | from ..models.pydantic.user import User 4 | 5 | 6 | async def send_message(ctx: dict, user_id: int, message: str): 7 | # TODO: Add a messaging service. 8 | orm_user: ORMUser = await ORMUser.get(user_id) 9 | user: User = User.from_orm(orm_user) 10 | print( 11 | "Message sent to {phone_number}: {message}".format( 12 | phone_number=user.phone_number, message=message 13 | ) 14 | ) 15 | return user 16 | -------------------------------------------------------------------------------- /app/worker.py: -------------------------------------------------------------------------------- 1 | # isort:skip_file 2 | 3 | import sys 4 | 5 | sys.path.extend(["./"]) 6 | 7 | from pydantic.utils import import_string 8 | 9 | from .application import db 10 | from .settings.arq import settings 11 | from .settings.globals import ARQ_BACKGROUND_FUNCTIONS, DATABASE_CONFIG 12 | 13 | FUNCTIONS: list = [ 14 | import_string(background_function) 15 | for background_function in list(ARQ_BACKGROUND_FUNCTIONS) 16 | ] if ARQ_BACKGROUND_FUNCTIONS is not None else list() 17 | 18 | 19 | async def startup(ctx): 20 | """ 21 | Binds a connection set to the db object. 22 | """ 23 | await db.set_bind(DATABASE_CONFIG.url) 24 | 25 | 26 | async def shutdown(ctx): 27 | """ 28 | Pops the bind on the db object. 29 | """ 30 | await db.pop_bind().close() 31 | 32 | 33 | class WorkerSettings: 34 | """ 35 | Settings for the ARQ worker. 36 | """ 37 | 38 | on_startup = startup 39 | on_shutdown = shutdown 40 | redis_settings = settings 41 | functions: list = FUNCTIONS 42 | -------------------------------------------------------------------------------- /docker-compose.local.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | container_name: fastapi-gino-arq-uvicorn 6 | image: fastapi-gino-arq-uvicorn 7 | environment: 8 | - REDIS_IP=cache 9 | - REDIS_PORT=6379 10 | - DB_HOST=database 11 | - DATABASE=postgres 12 | - DB_USER=postgres 13 | - DB_PASSWORD=postgres 14 | - DB_PORT=5432 15 | depends_on: 16 | - database 17 | - redis 18 | 19 | worker: 20 | container_name: fastapi-gino-arq-uvicorn-worker 21 | image: fastapi-gino-arq-uvicorn-worker 22 | depends_on: 23 | - database 24 | - redis 25 | environment: 26 | - REDIS_IP=cache 27 | - REDIS_PORT=6379 28 | - DB_HOST=database 29 | - DATABASE=postgres 30 | - DB_USER=postgres 31 | - DB_PASSWORD=postgres 32 | - DB_PORT=5432 33 | env_file: .env 34 | 35 | redis: 36 | image: redis 37 | container_name: cache 38 | expose: 39 | - 6379 40 | 41 | database: 42 | image: "postgres:latest" 43 | container_name: database 44 | restart: always 45 | ports: 46 | - 54320:5432 47 | environment: 48 | - POSTGRES_PASSWORD=postgres 49 | volumes: 50 | - database_data:/var/lib/postgresql/data 51 | 52 | volumes: 53 | database_data: 54 | -------------------------------------------------------------------------------- /docker-compose.worker.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | worker: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile.worker 8 | container_name: fastapi-gino-arq-uvicorn-worker 9 | image: fastapi-gino-arq-uvicorn-worker 10 | env_file: .env 11 | environment: 12 | - COMPOSE_CONVERT_WINDOWS_PATHS=1 13 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | container_name: fastapi-gino-arq-uvicorn 9 | image: fastapi-gino-arq-uvicorn 10 | env_file: .env 11 | environment: 12 | - COMPOSE_CONVERT_WINDOWS_PATHS=1 13 | ports: 14 | - 80:80 15 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | category = "main" 3 | description = "asyncio (PEP 3156) Redis support" 4 | name = "aioredis" 5 | optional = false 6 | python-versions = "*" 7 | version = "1.3.1" 8 | 9 | [package.dependencies] 10 | async-timeout = "*" 11 | hiredis = "*" 12 | 13 | [[package]] 14 | category = "main" 15 | description = "A database migration tool for SQLAlchemy." 16 | name = "alembic" 17 | optional = false 18 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 19 | version = "1.4.2" 20 | 21 | [package.dependencies] 22 | Mako = "*" 23 | SQLAlchemy = ">=1.1.0" 24 | python-dateutil = "*" 25 | python-editor = ">=0.3" 26 | 27 | [[package]] 28 | category = "dev" 29 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 30 | name = "appdirs" 31 | optional = false 32 | python-versions = "*" 33 | version = "1.4.4" 34 | 35 | [[package]] 36 | category = "main" 37 | description = "Job queues in python with asyncio and redis" 38 | name = "arq" 39 | optional = false 40 | python-versions = ">=3.6" 41 | version = "0.19" 42 | 43 | [package.dependencies] 44 | aioredis = ">=1.1.0" 45 | async-timeout = ">=3.0.0" 46 | click = ">=6.7" 47 | pydantic = ">=1" 48 | 49 | [package.extras] 50 | watch = ["watchgod (>=0.4)"] 51 | 52 | [[package]] 53 | category = "main" 54 | description = "Timeout context manager for asyncio programs" 55 | name = "async-timeout" 56 | optional = false 57 | python-versions = ">=3.5.3" 58 | version = "3.0.1" 59 | 60 | [[package]] 61 | category = "main" 62 | description = "An asyncio PostgreSQL driver" 63 | name = "asyncpg" 64 | optional = false 65 | python-versions = ">=3.5.0" 66 | version = "0.20.1" 67 | 68 | [package.extras] 69 | dev = ["Cython (0.29.14)", "pytest (>=3.6.0)", "Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)", "pycodestyle (>=2.5.0,<2.6.0)", "flake8 (>=3.7.9,<3.8.0)", "uvloop (>=0.14.0,<0.15.0)"] 70 | docs = ["Sphinx (>=1.7.3,<1.8.0)", "sphinxcontrib-asyncio (>=0.2.0,<0.3.0)", "sphinx-rtd-theme (>=0.2.4,<0.3.0)"] 71 | test = ["pycodestyle (>=2.5.0,<2.6.0)", "flake8 (>=3.7.9,<3.8.0)", "uvloop (>=0.14.0,<0.15.0)"] 72 | 73 | [[package]] 74 | category = "dev" 75 | description = "Classes Without Boilerplate" 76 | name = "attrs" 77 | optional = false 78 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 79 | version = "19.3.0" 80 | 81 | [package.extras] 82 | azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] 83 | dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] 84 | docs = ["sphinx", "zope.interface"] 85 | tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] 86 | 87 | [[package]] 88 | category = "dev" 89 | description = "Security oriented static analyser for python code." 90 | name = "bandit" 91 | optional = false 92 | python-versions = "*" 93 | version = "1.6.2" 94 | 95 | [package.dependencies] 96 | GitPython = ">=1.0.1" 97 | PyYAML = ">=3.13" 98 | colorama = ">=0.3.9" 99 | six = ">=1.10.0" 100 | stevedore = ">=1.20.0" 101 | 102 | [[package]] 103 | category = "dev" 104 | description = "The uncompromising code formatter." 105 | name = "black" 106 | optional = false 107 | python-versions = ">=3.6" 108 | version = "19.10b0" 109 | 110 | [package.dependencies] 111 | appdirs = "*" 112 | attrs = ">=18.1.0" 113 | click = ">=6.5" 114 | pathspec = ">=0.6,<1" 115 | regex = "*" 116 | toml = ">=0.9.4" 117 | typed-ast = ">=1.4.0" 118 | 119 | [package.extras] 120 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 121 | 122 | [[package]] 123 | category = "main" 124 | description = "Python package for providing Mozilla's CA Bundle." 125 | name = "certifi" 126 | optional = false 127 | python-versions = "*" 128 | version = "2020.4.5.1" 129 | 130 | [[package]] 131 | category = "dev" 132 | description = "Validate configuration and produce human readable error messages." 133 | name = "cfgv" 134 | optional = false 135 | python-versions = ">=3.6.1" 136 | version = "3.1.0" 137 | 138 | [[package]] 139 | category = "main" 140 | description = "Composable command line interface toolkit" 141 | name = "click" 142 | optional = false 143 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 144 | version = "7.1.2" 145 | 146 | [[package]] 147 | category = "dev" 148 | description = "Cross-platform colored terminal text." 149 | marker = "platform_system == \"Windows\"" 150 | name = "colorama" 151 | optional = false 152 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 153 | version = "0.4.3" 154 | 155 | [[package]] 156 | category = "dev" 157 | description = "Distribution utilities" 158 | name = "distlib" 159 | optional = false 160 | python-versions = "*" 161 | version = "0.3.0" 162 | 163 | [[package]] 164 | category = "main" 165 | description = "DNS toolkit" 166 | name = "dnspython" 167 | optional = false 168 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 169 | version = "1.16.0" 170 | 171 | [package.extras] 172 | DNSSEC = ["pycryptodome", "ecdsa (>=0.13)"] 173 | IDNA = ["idna (>=2.1)"] 174 | 175 | [[package]] 176 | category = "main" 177 | description = "A robust email syntax and deliverability validation library for Python 2.x/3.x." 178 | name = "email-validator" 179 | optional = false 180 | python-versions = "*" 181 | version = "1.1.0" 182 | 183 | [package.dependencies] 184 | dnspython = ">=1.15.0" 185 | idna = ">=2.0.0" 186 | 187 | [[package]] 188 | category = "main" 189 | description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" 190 | name = "fastapi" 191 | optional = false 192 | python-versions = ">=3.6" 193 | version = "0.54.1" 194 | 195 | [package.dependencies] 196 | pydantic = ">=0.32.2,<2.0.0" 197 | starlette = "0.13.2" 198 | 199 | [package.extras] 200 | all = ["requests", "aiofiles", "jinja2", "python-multipart", "itsdangerous", "pyyaml", "graphene", "ujson", "orjson", "email-validator", "uvicorn", "async-exit-stack", "async-generator"] 201 | dev = ["pyjwt", "passlib", "autoflake", "flake8", "uvicorn", "graphene"] 202 | doc = ["mkdocs", "mkdocs-material", "markdown-include", "typer", "typer-cli", "pyyaml"] 203 | test = ["pytest (>=4.0.0)", "pytest-cov", "mypy", "black", "isort", "requests", "email-validator", "sqlalchemy", "peewee", "databases", "orjson", "async-exit-stack", "async-generator", "python-multipart", "aiofiles", "flask"] 204 | 205 | [[package]] 206 | category = "dev" 207 | description = "A platform independent file lock." 208 | name = "filelock" 209 | optional = false 210 | python-versions = "*" 211 | version = "3.0.12" 212 | 213 | [[package]] 214 | category = "dev" 215 | description = "the modular source code checker: pep8 pyflakes and co" 216 | name = "flake8" 217 | optional = false 218 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 219 | version = "3.8.1" 220 | 221 | [package.dependencies] 222 | mccabe = ">=0.6.0,<0.7.0" 223 | pycodestyle = ">=2.6.0a1,<2.7.0" 224 | pyflakes = ">=2.2.0,<2.3.0" 225 | 226 | [[package]] 227 | category = "main" 228 | description = "GINO Is Not ORM - a Python asyncio ORM on SQLAlchemy core." 229 | name = "gino" 230 | optional = false 231 | python-versions = ">=3.5,<4.0" 232 | version = "1.0.0" 233 | 234 | [package.dependencies] 235 | SQLAlchemy = ">=1.2,<2.0" 236 | asyncpg = ">=0.18,<1.0" 237 | 238 | [package.extras] 239 | aiohttp = ["gino-aiohttp (>=0.1.0,<0.2.0)"] 240 | quart = ["gino-quart (>=0.1.0,<0.2.0)"] 241 | sanic = ["gino-sanic (>=0.1.0,<0.2.0)"] 242 | starlette = ["gino-starlette (>=0.1.1,<0.2.0)"] 243 | tornado = ["gino-tornado (>=0.1.0,<0.2.0)"] 244 | 245 | [[package]] 246 | category = "main" 247 | description = "An extension for GINO to integrate with Starlette" 248 | name = "gino-starlette" 249 | optional = false 250 | python-versions = ">=3.6,<4.0" 251 | version = "0.1.1" 252 | 253 | [package.dependencies] 254 | gino = ">=1.0.0rc2,<2.0.0" 255 | starlette = ">=0.13.0,<0.14.0" 256 | 257 | [[package]] 258 | category = "dev" 259 | description = "Git Object Database" 260 | name = "gitdb" 261 | optional = false 262 | python-versions = ">=3.4" 263 | version = "4.0.5" 264 | 265 | [package.dependencies] 266 | smmap = ">=3.0.1,<4" 267 | 268 | [[package]] 269 | category = "dev" 270 | description = "Python Git Library" 271 | name = "gitpython" 272 | optional = false 273 | python-versions = ">=3.4" 274 | version = "3.1.2" 275 | 276 | [package.dependencies] 277 | gitdb = ">=4.0.1,<5" 278 | 279 | [[package]] 280 | category = "main" 281 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 282 | name = "h11" 283 | optional = false 284 | python-versions = "*" 285 | version = "0.9.0" 286 | 287 | [[package]] 288 | category = "main" 289 | description = "Python wrapper for hiredis" 290 | name = "hiredis" 291 | optional = false 292 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 293 | version = "1.0.1" 294 | 295 | [[package]] 296 | category = "main" 297 | description = "A collection of framework independent HTTP protocol utils." 298 | marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" 299 | name = "httptools" 300 | optional = false 301 | python-versions = "*" 302 | version = "0.1.1" 303 | 304 | [package.extras] 305 | test = ["Cython (0.29.14)"] 306 | 307 | [[package]] 308 | category = "dev" 309 | description = "File identification library for Python" 310 | name = "identify" 311 | optional = false 312 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 313 | version = "1.4.15" 314 | 315 | [package.extras] 316 | license = ["editdistance"] 317 | 318 | [[package]] 319 | category = "main" 320 | description = "Internationalized Domain Names in Applications (IDNA)" 321 | name = "idna" 322 | optional = false 323 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 324 | version = "2.9" 325 | 326 | [[package]] 327 | category = "dev" 328 | description = "A Python utility / library to sort Python imports." 329 | name = "isort" 330 | optional = false 331 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 332 | version = "4.3.21" 333 | 334 | [package.dependencies] 335 | [package.dependencies.toml] 336 | optional = true 337 | version = "*" 338 | 339 | [package.extras] 340 | pipfile = ["pipreqs", "requirementslib"] 341 | pyproject = ["toml"] 342 | requirements = ["pipreqs", "pip-api"] 343 | xdg_home = ["appdirs (>=1.4.0)"] 344 | 345 | [[package]] 346 | category = "main" 347 | description = "A super-fast templating language that borrows the best ideas from the existing templating languages." 348 | name = "mako" 349 | optional = false 350 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 351 | version = "1.1.2" 352 | 353 | [package.dependencies] 354 | MarkupSafe = ">=0.9.2" 355 | 356 | [package.extras] 357 | babel = ["babel"] 358 | lingua = ["lingua"] 359 | 360 | [[package]] 361 | category = "main" 362 | description = "Safely add untrusted strings to HTML/XML markup." 363 | name = "markupsafe" 364 | optional = false 365 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 366 | version = "1.1.1" 367 | 368 | [[package]] 369 | category = "dev" 370 | description = "McCabe checker, plugin for flake8" 371 | name = "mccabe" 372 | optional = false 373 | python-versions = "*" 374 | version = "0.6.1" 375 | 376 | [[package]] 377 | category = "dev" 378 | description = "Node.js virtual environment builder" 379 | name = "nodeenv" 380 | optional = false 381 | python-versions = "*" 382 | version = "1.3.5" 383 | 384 | [[package]] 385 | category = "dev" 386 | description = "Utility library for gitignore style pattern matching of file paths." 387 | name = "pathspec" 388 | optional = false 389 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 390 | version = "0.8.0" 391 | 392 | [[package]] 393 | category = "dev" 394 | description = "Python Build Reasonableness" 395 | name = "pbr" 396 | optional = false 397 | python-versions = "*" 398 | version = "5.4.5" 399 | 400 | [[package]] 401 | category = "dev" 402 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 403 | name = "pre-commit" 404 | optional = false 405 | python-versions = ">=3.6.1" 406 | version = "2.4.0" 407 | 408 | [package.dependencies] 409 | cfgv = ">=2.0.0" 410 | identify = ">=1.0.0" 411 | nodeenv = ">=0.11.1" 412 | pyyaml = ">=5.1" 413 | toml = "*" 414 | virtualenv = ">=20.0.8" 415 | 416 | [[package]] 417 | category = "main" 418 | description = "psycopg2 - Python-PostgreSQL Database Adapter" 419 | name = "psycopg2-binary" 420 | optional = false 421 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" 422 | version = "2.8.5" 423 | 424 | [[package]] 425 | category = "dev" 426 | description = "Python style guide checker" 427 | name = "pycodestyle" 428 | optional = false 429 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 430 | version = "2.6.0" 431 | 432 | [[package]] 433 | category = "main" 434 | description = "Data validation and settings management using python 3.6 type hinting" 435 | name = "pydantic" 436 | optional = false 437 | python-versions = ">=3.6" 438 | version = "1.5.1" 439 | 440 | [package.extras] 441 | dotenv = ["python-dotenv (>=0.10.4)"] 442 | email = ["email-validator (>=1.0.3)"] 443 | typing_extensions = ["typing-extensions (>=3.7.2)"] 444 | 445 | [[package]] 446 | category = "dev" 447 | description = "passive checker of Python programs" 448 | name = "pyflakes" 449 | optional = false 450 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 451 | version = "2.2.0" 452 | 453 | [[package]] 454 | category = "main" 455 | description = "Extensions to the standard Python datetime module" 456 | name = "python-dateutil" 457 | optional = false 458 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 459 | version = "2.8.1" 460 | 461 | [package.dependencies] 462 | six = ">=1.5" 463 | 464 | [[package]] 465 | category = "main" 466 | description = "Programmatically open an editor, capture the result." 467 | name = "python-editor" 468 | optional = false 469 | python-versions = "*" 470 | version = "1.0.4" 471 | 472 | [[package]] 473 | category = "dev" 474 | description = "YAML parser and emitter for Python" 475 | name = "pyyaml" 476 | optional = false 477 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 478 | version = "5.3.1" 479 | 480 | [[package]] 481 | category = "dev" 482 | description = "Alternative regular expression module, to replace re." 483 | name = "regex" 484 | optional = false 485 | python-versions = "*" 486 | version = "2020.5.7" 487 | 488 | [[package]] 489 | category = "dev" 490 | description = "a python refactoring library..." 491 | name = "rope" 492 | optional = false 493 | python-versions = "*" 494 | version = "0.17.0" 495 | 496 | [package.extras] 497 | dev = ["pytest"] 498 | 499 | [[package]] 500 | category = "main" 501 | description = "Python client for Sentry (https://getsentry.com)" 502 | name = "sentry-sdk" 503 | optional = false 504 | python-versions = "*" 505 | version = "0.14.3" 506 | 507 | [package.dependencies] 508 | certifi = "*" 509 | urllib3 = ">=1.10.0" 510 | 511 | [package.extras] 512 | aiohttp = ["aiohttp (>=3.5)"] 513 | beam = ["beam (>=2.12)"] 514 | bottle = ["bottle (>=0.12.13)"] 515 | celery = ["celery (>=3)"] 516 | django = ["django (>=1.8)"] 517 | falcon = ["falcon (>=1.4)"] 518 | flask = ["flask (>=0.11)", "blinker (>=1.1)"] 519 | pyspark = ["pyspark (>=2.4.4)"] 520 | rq = ["0.6"] 521 | sanic = ["sanic (>=0.8)"] 522 | sqlalchemy = ["sqlalchemy (>=1.2)"] 523 | tornado = ["tornado (>=5)"] 524 | 525 | [[package]] 526 | category = "main" 527 | description = "Python 2 and 3 compatibility utilities" 528 | name = "six" 529 | optional = false 530 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 531 | version = "1.14.0" 532 | 533 | [[package]] 534 | category = "dev" 535 | description = "A pure Python implementation of a sliding window memory map manager" 536 | name = "smmap" 537 | optional = false 538 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 539 | version = "3.0.4" 540 | 541 | [[package]] 542 | category = "main" 543 | description = "Database Abstraction Library" 544 | name = "sqlalchemy" 545 | optional = false 546 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 547 | version = "1.3.16" 548 | 549 | [package.extras] 550 | mssql = ["pyodbc"] 551 | mssql_pymssql = ["pymssql"] 552 | mssql_pyodbc = ["pyodbc"] 553 | mysql = ["mysqlclient"] 554 | oracle = ["cx-oracle"] 555 | postgresql = ["psycopg2"] 556 | postgresql_pg8000 = ["pg8000"] 557 | postgresql_psycopg2binary = ["psycopg2-binary"] 558 | postgresql_psycopg2cffi = ["psycopg2cffi"] 559 | pymysql = ["pymysql"] 560 | 561 | [[package]] 562 | category = "main" 563 | description = "Various utility functions for SQLAlchemy." 564 | name = "sqlalchemy-utils" 565 | optional = false 566 | python-versions = "*" 567 | version = "0.36.5" 568 | 569 | [package.dependencies] 570 | SQLAlchemy = ">=1.0" 571 | six = "*" 572 | 573 | [package.extras] 574 | anyjson = ["anyjson (>=0.3.3)"] 575 | arrow = ["arrow (>=0.3.4)"] 576 | babel = ["Babel (>=1.3)"] 577 | color = ["colour (>=0.0.4)"] 578 | encrypted = ["cryptography (>=0.6)"] 579 | intervals = ["intervals (>=0.7.1)"] 580 | password = ["passlib (>=1.6,<2.0)"] 581 | pendulum = ["pendulum (>=2.0.5)"] 582 | phone = ["phonenumbers (>=5.9.2)"] 583 | test = ["pytest (>=2.7.1)", "Pygments (>=1.2)", "Jinja2 (>=2.3)", "docutils (>=0.10)", "flexmock (>=0.9.7)", "mock (2.0.0)", "psycopg2 (>=2.5.1)", "pg8000 (>=1.12.4)", "pytz (>=2014.2)", "python-dateutil (>=2.6)", "pymysql", "flake8 (>=2.4.0)", "isort (>=4.2.2)", "pyodbc"] 584 | test_all = ["anyjson (>=0.3.3)", "arrow (>=0.3.4)", "Babel (>=1.3)", "colour (>=0.0.4)", "cryptography (>=0.6)", "intervals (>=0.7.1)", "passlib (>=1.6,<2.0)", "pendulum (>=2.0.5)", "phonenumbers (>=5.9.2)", "pytest (>=2.7.1)", "Pygments (>=1.2)", "Jinja2 (>=2.3)", "docutils (>=0.10)", "flexmock (>=0.9.7)", "mock (2.0.0)", "psycopg2 (>=2.5.1)", "pg8000 (>=1.12.4)", "pytz (>=2014.2)", "python-dateutil (>=2.6)", "pymysql", "flake8 (>=2.4.0)", "isort (>=4.2.2)", "pyodbc", "python-dateutil", "furl (>=0.4.1)"] 585 | timezone = ["python-dateutil"] 586 | url = ["furl (>=0.4.1)"] 587 | 588 | [[package]] 589 | category = "main" 590 | description = "The little ASGI library that shines." 591 | name = "starlette" 592 | optional = false 593 | python-versions = ">=3.6" 594 | version = "0.13.2" 595 | 596 | [package.extras] 597 | full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests", "ujson"] 598 | 599 | [[package]] 600 | category = "dev" 601 | description = "Manage dynamic plugins for Python applications" 602 | name = "stevedore" 603 | optional = false 604 | python-versions = "*" 605 | version = "1.32.0" 606 | 607 | [package.dependencies] 608 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 609 | six = ">=1.10.0" 610 | 611 | [[package]] 612 | category = "dev" 613 | description = "tasks runner for python projects" 614 | name = "taskipy" 615 | optional = false 616 | python-versions = ">=3.6,<4.0" 617 | version = "1.2.1" 618 | 619 | [package.dependencies] 620 | toml = ">=0.10.0,<0.11.0" 621 | 622 | [[package]] 623 | category = "dev" 624 | description = "Python Library for Tom's Obvious, Minimal Language" 625 | name = "toml" 626 | optional = false 627 | python-versions = "*" 628 | version = "0.10.0" 629 | 630 | [[package]] 631 | category = "dev" 632 | description = "a fork of Python 2 and 3 ast modules with type comment support" 633 | name = "typed-ast" 634 | optional = false 635 | python-versions = "*" 636 | version = "1.4.1" 637 | 638 | [[package]] 639 | category = "main" 640 | description = "HTTP library with thread-safe connection pooling, file post, and more." 641 | name = "urllib3" 642 | optional = false 643 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 644 | version = "1.25.9" 645 | 646 | [package.extras] 647 | brotli = ["brotlipy (>=0.6.0)"] 648 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"] 649 | socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"] 650 | 651 | [[package]] 652 | category = "main" 653 | description = "The lightning-fast ASGI server." 654 | name = "uvicorn" 655 | optional = false 656 | python-versions = "*" 657 | version = "0.11.5" 658 | 659 | [package.dependencies] 660 | click = ">=7.0.0,<8.0.0" 661 | h11 = ">=0.8,<0.10" 662 | httptools = ">=0.1.0,<0.2.0" 663 | uvloop = ">=0.14.0" 664 | websockets = ">=8.0.0,<9.0.0" 665 | 666 | [package.extras] 667 | watchgodreload = ["watchgod (>=0.6,<0.7)"] 668 | 669 | [[package]] 670 | category = "main" 671 | description = "Fast implementation of asyncio event loop on top of libuv" 672 | marker = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"" 673 | name = "uvloop" 674 | optional = false 675 | python-versions = "*" 676 | version = "0.14.0" 677 | 678 | [[package]] 679 | category = "dev" 680 | description = "Virtual Python Environment builder" 681 | name = "virtualenv" 682 | optional = false 683 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" 684 | version = "20.0.20" 685 | 686 | [package.dependencies] 687 | appdirs = ">=1.4.3,<2" 688 | distlib = ">=0.3.0,<1" 689 | filelock = ">=3.0.0,<4" 690 | six = ">=1.9.0,<2" 691 | 692 | [package.extras] 693 | docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] 694 | testing = ["pytest (>=4)", "coverage (>=5)", "coverage-enable-subprocess (>=1)", "pytest-xdist (>=1.31.0)", "pytest-mock (>=2)", "pytest-env (>=0.6.2)", "pytest-randomly (>=1)", "pytest-timeout", "packaging (>=20.0)", "xonsh (>=0.9.16)"] 695 | 696 | [[package]] 697 | category = "dev" 698 | description = "Simple, modern file watching and code reload in python." 699 | name = "watchgod" 700 | optional = false 701 | python-versions = ">=3.5" 702 | version = "0.6" 703 | 704 | [[package]] 705 | category = "main" 706 | description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" 707 | name = "websockets" 708 | optional = false 709 | python-versions = ">=3.6.1" 710 | version = "8.1" 711 | 712 | [metadata] 713 | content-hash = "3dd14972fac519cde9117da78ec8a1f9701420a5549833396ec1dbc0b350f63b" 714 | python-versions = "^3.8" 715 | 716 | [metadata.files] 717 | aioredis = [ 718 | {file = "aioredis-1.3.1-py3-none-any.whl", hash = "sha256:b61808d7e97b7cd5a92ed574937a079c9387fdadd22bfbfa7ad2fd319ecc26e3"}, 719 | {file = "aioredis-1.3.1.tar.gz", hash = "sha256:15f8af30b044c771aee6787e5ec24694c048184c7b9e54c3b60c750a4b93273a"}, 720 | ] 721 | alembic = [ 722 | {file = "alembic-1.4.2.tar.gz", hash = "sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf"}, 723 | ] 724 | appdirs = [ 725 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 726 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 727 | ] 728 | arq = [ 729 | {file = "arq-0.19-py3-none-any.whl", hash = "sha256:a78fb5c031a44f94d3c8f1bd905aba6fe1d62dcc216e9a7ac7cc12efd4feecc5"}, 730 | {file = "arq-0.19.tar.gz", hash = "sha256:9a42d97a32b92ec78a51bec53f8b50ae343cba1ba178d4b5edbf798cfada7f6b"}, 731 | ] 732 | async-timeout = [ 733 | {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, 734 | {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, 735 | ] 736 | asyncpg = [ 737 | {file = "asyncpg-0.20.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:f7184689177eeb5a11fa1b2baf3f6f2e26bfd7a85acf4de1a3adbd0867d7c0e2"}, 738 | {file = "asyncpg-0.20.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f0c9719ac00615f097fe91082b785bce36dbf02a5ec4115ede0ebfd2cd9500cb"}, 739 | {file = "asyncpg-0.20.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:1388caa456070dab102be874205e3ae8fd1de2577d5de9fa22e65ba5c0f8b110"}, 740 | {file = "asyncpg-0.20.1-cp35-cp35m-win32.whl", hash = "sha256:ec6e7046c98730cb2ba4df41387e10cb8963a3ac2918f69ae416f8aab9ca7b1b"}, 741 | {file = "asyncpg-0.20.1-cp35-cp35m-win_amd64.whl", hash = "sha256:25edb0b947eb632b6b53e5a4b36cba5677297bb34cbaba270019714d0a5fed76"}, 742 | {file = "asyncpg-0.20.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:95cd2df61ee00b789bdcd04a080e6d9188693b841db2bf9a87ebaed9e53147e0"}, 743 | {file = "asyncpg-0.20.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:058baec9d6b75612412baa872a1aa47317d0ff88c318a49f9c4a2389043d5a8d"}, 744 | {file = "asyncpg-0.20.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c773c7dbe2f4d3ebc9e3030e94303e45d6742e6c2fc25da0c46a56ea3d83caeb"}, 745 | {file = "asyncpg-0.20.1-cp36-cp36m-win32.whl", hash = "sha256:5664d1bd8abe64fc60a0e701eb85fa1d8c9a4a8018a5a59164d27238f2caf395"}, 746 | {file = "asyncpg-0.20.1-cp36-cp36m-win_amd64.whl", hash = "sha256:57666dfae38f4dbf84ffbf0c5c0f78733fef0e8e083230275dcb9ccad1d5ee09"}, 747 | {file = "asyncpg-0.20.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:0c336903c3b08e970f8af2f606332f1738dba156bca83ed0467dc2f5c70da796"}, 748 | {file = "asyncpg-0.20.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ad5ba062e09673b1a4b8d0facaf5a6d9719bf7b337440d10b07fe994d90a9552"}, 749 | {file = "asyncpg-0.20.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba90d3578bc6dddcbce461875672fd9bdb34f0b8215b68612dd3b65a956ff51c"}, 750 | {file = "asyncpg-0.20.1-cp37-cp37m-win32.whl", hash = "sha256:da238592235717419a6a7b5edc8564da410ebfd056ca4ecc41e70b1b5df86fba"}, 751 | {file = "asyncpg-0.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:74510234c294c6a6767089ba9c938f09a491426c24405634eb357bd91dffd734"}, 752 | {file = "asyncpg-0.20.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:391aea89871df8c1560750af6c7170f2772c2d133b34772acf3637e3cf4db93e"}, 753 | {file = "asyncpg-0.20.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a981500bf6947926e53c48f4d60ae080af1b4ad7fa78e363465a5b5ad4f2b65e"}, 754 | {file = "asyncpg-0.20.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a9e6fd6f0f9e8bd77e9a4e1ef9a4f83a80674d9136a754ae3603e915da96b627"}, 755 | {file = "asyncpg-0.20.1-cp38-cp38-win32.whl", hash = "sha256:e39aac2b3a2f839ce65aa255ce416de899c58b7d38d601d24ca35558e13b48e3"}, 756 | {file = "asyncpg-0.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:2af6a5a705accd36e13292ea43d08c20b15e52d684beb522cb3a7d3c9c8f3f48"}, 757 | {file = "asyncpg-0.20.1.tar.gz", hash = "sha256:394bf19bdddbba07a38cd6fb526ebf66e120444d6b3097332b78efd5b26495b0"}, 758 | ] 759 | attrs = [ 760 | {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, 761 | {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, 762 | ] 763 | bandit = [ 764 | {file = "bandit-1.6.2-py2.py3-none-any.whl", hash = "sha256:336620e220cf2d3115877685e264477ff9d9abaeb0afe3dc7264f55fa17a3952"}, 765 | {file = "bandit-1.6.2.tar.gz", hash = "sha256:41e75315853507aa145d62a78a2a6c5e3240fe14ee7c601459d0df9418196065"}, 766 | ] 767 | black = [ 768 | {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, 769 | {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, 770 | ] 771 | certifi = [ 772 | {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, 773 | {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, 774 | ] 775 | cfgv = [ 776 | {file = "cfgv-3.1.0-py2.py3-none-any.whl", hash = "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53"}, 777 | {file = "cfgv-3.1.0.tar.gz", hash = "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"}, 778 | ] 779 | click = [ 780 | {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, 781 | {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, 782 | ] 783 | colorama = [ 784 | {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, 785 | {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, 786 | ] 787 | distlib = [ 788 | {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, 789 | ] 790 | dnspython = [ 791 | {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, 792 | {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, 793 | ] 794 | email-validator = [ 795 | {file = "email_validator-1.1.0-py2.py3-none-any.whl", hash = "sha256:de2aad89b13574a73fd26c04a5200389b293bd9578602fc3b51537ada47af153"}, 796 | {file = "email_validator-1.1.0.tar.gz", hash = "sha256:2d515ed56eca41a2c91bbe1e0b1054d604a2502417487b553d3d26962945eda2"}, 797 | ] 798 | fastapi = [ 799 | {file = "fastapi-0.54.1-py3-none-any.whl", hash = "sha256:1ee9a49f28d510b62b3b51a9452b274853bfc9c5d4b947ed054366e2d49f9efa"}, 800 | {file = "fastapi-0.54.1.tar.gz", hash = "sha256:72f40f47e5235cb5cbbad1d4f97932ede6059290c07e12e9784028dcd1063d28"}, 801 | ] 802 | filelock = [ 803 | {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, 804 | {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, 805 | ] 806 | flake8 = [ 807 | {file = "flake8-3.8.1-py2.py3-none-any.whl", hash = "sha256:6c1193b0c3f853ef763969238f6c81e9e63ace9d024518edc020d5f1d6d93195"}, 808 | {file = "flake8-3.8.1.tar.gz", hash = "sha256:ea6623797bf9a52f4c9577d780da0bb17d65f870213f7b5bcc9fca82540c31d5"}, 809 | ] 810 | gino = [ 811 | {file = "gino-1.0.0-py3-none-any.whl", hash = "sha256:5750575d25b458ac8c484f324310042485ecd8039ae67fc82bc34e06182c2737"}, 812 | {file = "gino-1.0.0.tar.gz", hash = "sha256:0924f16e02f33f046ad3aa9bc02a7ac7cd72f89261f4447ab54fd789eb3b65fc"}, 813 | ] 814 | gino-starlette = [ 815 | {file = "gino-starlette-0.1.1.tar.gz", hash = "sha256:a1afe419b34146449a502a5483085a60a75a46639534fff50510172b47c930fb"}, 816 | {file = "gino_starlette-0.1.1-py3-none-any.whl", hash = "sha256:de6ec87168097a52668359c842e9e3be4d339423c7805c615377975a1a19cb6c"}, 817 | ] 818 | gitdb = [ 819 | {file = "gitdb-4.0.5-py3-none-any.whl", hash = "sha256:91f36bfb1ab7949b3b40e23736db18231bf7593edada2ba5c3a174a7b23657ac"}, 820 | {file = "gitdb-4.0.5.tar.gz", hash = "sha256:c9e1f2d0db7ddb9a704c2a0217be31214e91a4fe1dea1efad19ae42ba0c285c9"}, 821 | ] 822 | gitpython = [ 823 | {file = "GitPython-3.1.2-py3-none-any.whl", hash = "sha256:da3b2cf819974789da34f95ac218ef99f515a928685db141327c09b73dd69c09"}, 824 | {file = "GitPython-3.1.2.tar.gz", hash = "sha256:864a47472548f3ba716ca202e034c1900f197c0fb3a08f641c20c3cafd15ed94"}, 825 | ] 826 | h11 = [ 827 | {file = "h11-0.9.0-py2.py3-none-any.whl", hash = "sha256:4bc6d6a1238b7615b266ada57e0618568066f57dd6fa967d1290ec9309b2f2f1"}, 828 | {file = "h11-0.9.0.tar.gz", hash = "sha256:33d4bca7be0fa039f4e84d50ab00531047e53d6ee8ffbc83501ea602c169cae1"}, 829 | ] 830 | hiredis = [ 831 | {file = "hiredis-1.0.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:38437a681f17c975fd22349e72c29bc643f8e7eb2d6dc5df419eac59afa4d7ce"}, 832 | {file = "hiredis-1.0.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:102f9b9dc6ed57feb3a7c9bdf7e71cb7c278fe8df1edfcfe896bc3e0c2be9447"}, 833 | {file = "hiredis-1.0.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:dbaef9a21a4f10bc281684ee4124f169e62bb533c2a92b55f8c06f64f9af7b8f"}, 834 | {file = "hiredis-1.0.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:03ed34a13316d0c34213c4fd46e0fa3a5299073f4d4f08e93fed8c2108b399b3"}, 835 | {file = "hiredis-1.0.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:d6456afeddba036def1a36d8a2758eca53202308d83db20ab5d0b66590919627"}, 836 | {file = "hiredis-1.0.1-cp27-cp27m-win32.whl", hash = "sha256:4a60e71625a2d78d8ab84dfb2fa2cfd9458c964b6e6c04fea76d9ade153fb371"}, 837 | {file = "hiredis-1.0.1-cp27-cp27m-win_amd64.whl", hash = "sha256:091eb38fbf968d1c5b703e412bbbd25f43a7967d8400842cee33a5a07b33c27b"}, 838 | {file = "hiredis-1.0.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:75c65c3850e89e9daa68d1b9bedd5806f177d60aa5a7b0953b4829481cfc1f72"}, 839 | {file = "hiredis-1.0.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:fa2dc05b87d97acc1c6ae63f3e0f39eae5246565232484b08db6bf2dc1580678"}, 840 | {file = "hiredis-1.0.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:74b364b3f06c9cf0a53f7df611045bc9437ed972a283fa1f0b12537236d23ddc"}, 841 | {file = "hiredis-1.0.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:9afeb88c67bbc663b9f27385c496da056d06ad87f55df6e393e1516cfecb0461"}, 842 | {file = "hiredis-1.0.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:040436e91df5143aff9e0debb49530d0b17a6bd52200ce568621c31ef581b10d"}, 843 | {file = "hiredis-1.0.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2b4b392c7e3082860c8371fab3ae762139090f9115819e12d9f56060f9ede05d"}, 844 | {file = "hiredis-1.0.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:936aa565e673536e8a211e43ec43197406f24cd1f290138bd143765079c8ba00"}, 845 | {file = "hiredis-1.0.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:48c627581ad4ef60adbac980981407939acf13a0e18f093502c7b542223c4f19"}, 846 | {file = "hiredis-1.0.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9d62cc7880110e4f83b0a51d218f465d3095e2751fbddd34e553dbd106a929ff"}, 847 | {file = "hiredis-1.0.1-cp35-cp35m-win32.whl", hash = "sha256:8113a7d5e87ecf57cd4ae263cc9e429adb9a3e59f5a7768da5d3312a8d0a051a"}, 848 | {file = "hiredis-1.0.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fe7d6ce9f6a5fbe24f09d95ea93e9c7271abc4e1565da511e1449b107b4d7848"}, 849 | {file = "hiredis-1.0.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:efc98b14ee3a8595e40b1425e8d42f5fd26f11a7b215a81ef9259068931754f4"}, 850 | {file = "hiredis-1.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:01ff0900134166961c9e339df77c33b72f7edc5cb41739f0babcd9faa345926e"}, 851 | {file = "hiredis-1.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d0caf98dfb8af395d6732bd16561c0a2458851bea522e39f12f04802dbf6f502"}, 852 | {file = "hiredis-1.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ae2ee0992f8de249715435942137843a93db204dd7db1e7cc9bdc5a8436443e8"}, 853 | {file = "hiredis-1.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:4414a96c212e732723b5c3d7c04d386ebbb2ec359e1de646322cbc3f875cbd0d"}, 854 | {file = "hiredis-1.0.1-cp36-cp36m-win32.whl", hash = "sha256:7f052de8bf744730a9120dbdc67bfeb7605a01f69fb8e7ba5c475af33c24e145"}, 855 | {file = "hiredis-1.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:2fa65a9df683bca72073cd77709ddeb289ea2b114d3775d225fbbcc5faf808c5"}, 856 | {file = "hiredis-1.0.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:585ace09f434e43d8a8dbeb366865b1a044d7c06319b3c7372a0a00e63b860f4"}, 857 | {file = "hiredis-1.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:bcbf9379c553b5facc6c04c1e5569b44b38ff16bcbf354676287698d61ee0c92"}, 858 | {file = "hiredis-1.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:3b3428fa3cf1ee178807b52c9bee8950ab94cd4eaa9bfae8c1bbae3c49501d34"}, 859 | {file = "hiredis-1.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:cbccbda6f1c62ab460449d9c85fdf24d0d32a6bf45176581151e53cc26a5d910"}, 860 | {file = "hiredis-1.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:2c9cc0b986397b833073f466e6b9e9c70d1d4dc2c2c1b3e9cae3a23102ff296c"}, 861 | {file = "hiredis-1.0.1-cp37-cp37m-win32.whl", hash = "sha256:84857ce239eb8ed191ac78e77ff65d52902f00f30f4ee83bf80eb71da73b70e6"}, 862 | {file = "hiredis-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:b36842d7cf32929d568f37ec5b3173b72b2ec6572dec4d6be6ce774762215aee"}, 863 | {file = "hiredis-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3dd8c2fae7f5494978facb0e93297dd627b1a3f536f3b070cf0a7d9157a07dcb"}, 864 | {file = "hiredis-1.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:01b577f84c20ecc9c07fc4c184231b08e3c3942de096fa99978e053de231c423"}, 865 | {file = "hiredis-1.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a1fadd062fc8d647ff39220c57ea2b48c99bb73f18223828ec97f88fc27e7898"}, 866 | {file = "hiredis-1.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:dce84916c09aaece006272b37234ae84a8ed13abb3a4d341a23933b8701abfb5"}, 867 | {file = "hiredis-1.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:8644a48ddc4a40b3e3a6b9443f396c2ee353afb2d45656c4fc68d04a82e8e3f7"}, 868 | {file = "hiredis-1.0.1-cp38-cp38-win32.whl", hash = "sha256:eb8c9c8b9869539d58d60ff4a28373a22514d40495911451343971cb4835b7a9"}, 869 | {file = "hiredis-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:a7754a783b1e5d6f627c19d099b178059c62f782ab62b4d8ba165b9fbc2ee34c"}, 870 | {file = "hiredis-1.0.1.tar.gz", hash = "sha256:aa59dd63bb3f736de4fc2d080114429d5d369dfb3265f771778e8349d67a97a4"}, 871 | ] 872 | httptools = [ 873 | {file = "httptools-0.1.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:a2719e1d7a84bb131c4f1e0cb79705034b48de6ae486eb5297a139d6a3296dce"}, 874 | {file = "httptools-0.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:fa3cd71e31436911a44620473e873a256851e1f53dee56669dae403ba41756a4"}, 875 | {file = "httptools-0.1.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:86c6acd66765a934e8730bf0e9dfaac6fdcf2a4334212bd4a0a1c78f16475ca6"}, 876 | {file = "httptools-0.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bc3114b9edbca5a1eb7ae7db698c669eb53eb8afbbebdde116c174925260849c"}, 877 | {file = "httptools-0.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:ac0aa11e99454b6a66989aa2d44bca41d4e0f968e395a0a8f164b401fefe359a"}, 878 | {file = "httptools-0.1.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:96da81e1992be8ac2fd5597bf0283d832287e20cb3cfde8996d2b00356d4e17f"}, 879 | {file = "httptools-0.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:56b6393c6ac7abe632f2294da53f30d279130a92e8ae39d8d14ee2e1b05ad1f2"}, 880 | {file = "httptools-0.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:96eb359252aeed57ea5c7b3d79839aaa0382c9d3149f7d24dd7172b1bcecb009"}, 881 | {file = "httptools-0.1.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:fea04e126014169384dee76a153d4573d90d0cbd1d12185da089f73c78390437"}, 882 | {file = "httptools-0.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:3592e854424ec94bd17dc3e0c96a64e459ec4147e6d53c0a42d0ebcef9cb9c5d"}, 883 | {file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"}, 884 | {file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"}, 885 | ] 886 | identify = [ 887 | {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, 888 | {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, 889 | ] 890 | idna = [ 891 | {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, 892 | {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, 893 | ] 894 | isort = [ 895 | {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, 896 | {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, 897 | ] 898 | mako = [ 899 | {file = "Mako-1.1.2-py2.py3-none-any.whl", hash = "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9"}, 900 | {file = "Mako-1.1.2.tar.gz", hash = "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d"}, 901 | ] 902 | markupsafe = [ 903 | {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, 904 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, 905 | {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, 906 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, 907 | {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, 908 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, 909 | {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, 910 | {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, 911 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, 912 | {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, 913 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, 914 | {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, 915 | {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, 916 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, 917 | {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, 918 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, 919 | {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, 920 | {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, 921 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, 922 | {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, 923 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, 924 | {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, 925 | {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, 926 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, 927 | {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, 928 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, 929 | {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, 930 | {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, 931 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, 932 | {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, 933 | {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, 934 | {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, 935 | {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, 936 | ] 937 | mccabe = [ 938 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 939 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 940 | ] 941 | nodeenv = [ 942 | {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, 943 | ] 944 | pathspec = [ 945 | {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, 946 | {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, 947 | ] 948 | pbr = [ 949 | {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, 950 | {file = "pbr-5.4.5.tar.gz", hash = "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c"}, 951 | ] 952 | pre-commit = [ 953 | {file = "pre_commit-2.4.0-py2.py3-none-any.whl", hash = "sha256:5559e09afcac7808933951ffaf4ff9aac524f31efbc3f24d021540b6c579813c"}, 954 | {file = "pre_commit-2.4.0.tar.gz", hash = "sha256:703e2e34cbe0eedb0d319eff9f7b83e2022bb5a3ab5289a6a8841441076514d0"}, 955 | ] 956 | psycopg2-binary = [ 957 | {file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"}, 958 | {file = "psycopg2_binary-2.8.5-cp27-cp27m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:96d3038f5bd061401996614f65d27a4ecb62d843eb4f48e212e6d129171a721f"}, 959 | {file = "psycopg2_binary-2.8.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:08507efbe532029adee21b8d4c999170a83760d38249936038bd0602327029b5"}, 960 | {file = "psycopg2_binary-2.8.5-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:b9a8b391c2b0321e0cd7ec6b4cfcc3dd6349347bd1207d48bcb752aa6c553a66"}, 961 | {file = "psycopg2_binary-2.8.5-cp27-cp27m-win32.whl", hash = "sha256:3286541b9d85a340ee4ed42732d15fc1bb441dc500c97243a768154ab8505bb5"}, 962 | {file = "psycopg2_binary-2.8.5-cp27-cp27m-win_amd64.whl", hash = "sha256:008da3ab51adc70a5f1cfbbe5db3a22607ab030eb44bcecf517ad11a0c2b3cac"}, 963 | {file = "psycopg2_binary-2.8.5-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:ba13346ff6d3eb2dca0b6fa0d8a9d999eff3dcd9b55f3a890f12b0b6362b2b38"}, 964 | {file = "psycopg2_binary-2.8.5-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:c8830b7d5f16fd79d39b21e3d94f247219036b29b30c8270314c46bf8b732389"}, 965 | {file = "psycopg2_binary-2.8.5-cp34-cp34m-win32.whl", hash = "sha256:51f7823f1b087d2020d8e8c9e6687473d3d239ba9afc162d9b2ab6e80b53f9f9"}, 966 | {file = "psycopg2_binary-2.8.5-cp34-cp34m-win_amd64.whl", hash = "sha256:107d9be3b614e52a192719c6bf32e8813030020ea1d1215daa86ded9a24d8b04"}, 967 | {file = "psycopg2_binary-2.8.5-cp35-cp35m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:930315ac53dc65cbf52ab6b6d27422611f5fb461d763c531db229c7e1af6c0b3"}, 968 | {file = "psycopg2_binary-2.8.5-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6bb2dd006a46a4a4ce95201f836194eb6a1e863f69ee5bab506673e0ca767057"}, 969 | {file = "psycopg2_binary-2.8.5-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:3939cf75fc89c5e9ed836e228c4a63604dff95ad19aed2bbf71d5d04c15ed5ce"}, 970 | {file = "psycopg2_binary-2.8.5-cp35-cp35m-win32.whl", hash = "sha256:a20299ee0ea2f9cca494396ac472d6e636745652a64a418b39522c120fd0a0a4"}, 971 | {file = "psycopg2_binary-2.8.5-cp35-cp35m-win_amd64.whl", hash = "sha256:cc30cb900f42c8a246e2cb76539d9726f407330bc244ca7729c41a44e8d807fb"}, 972 | {file = "psycopg2_binary-2.8.5-cp36-cp36m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:40abc319f7f26c042a11658bf3dd3b0b3bceccf883ec1c565d5c909a90204434"}, 973 | {file = "psycopg2_binary-2.8.5-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:702f09d8f77dc4794651f650828791af82f7c2efd8c91ae79e3d9fe4bb7d4c98"}, 974 | {file = "psycopg2_binary-2.8.5-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d1a8b01f6a964fec702d6b6dac1f91f2b9f9fe41b310cbb16c7ef1fac82df06d"}, 975 | {file = "psycopg2_binary-2.8.5-cp36-cp36m-win32.whl", hash = "sha256:17a0ea0b0eabf07035e5e0d520dabc7950aeb15a17c6d36128ba99b2721b25b1"}, 976 | {file = "psycopg2_binary-2.8.5-cp36-cp36m-win_amd64.whl", hash = "sha256:e004db88e5a75e5fdab1620fb9f90c9598c2a195a594225ac4ed2a6f1c23e162"}, 977 | {file = "psycopg2_binary-2.8.5-cp37-cp37m-macosx_10_6_intel.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:a34826d6465c2e2bbe9d0605f944f19d2480589f89863ed5f091943be27c9de4"}, 978 | {file = "psycopg2_binary-2.8.5-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:cac918cd7c4c498a60f5d2a61d4f0a6091c2c9490d81bc805c963444032d0dab"}, 979 | {file = "psycopg2_binary-2.8.5-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:7b832d76cc65c092abd9505cc670c4e3421fd136fb6ea5b94efbe4c146572505"}, 980 | {file = "psycopg2_binary-2.8.5-cp37-cp37m-win32.whl", hash = "sha256:bb0608694a91db1e230b4a314e8ed00ad07ed0c518f9a69b83af2717e31291a3"}, 981 | {file = "psycopg2_binary-2.8.5-cp37-cp37m-win_amd64.whl", hash = "sha256:eb2f43ae3037f1ef5e19339c41cf56947021ac892f668765cd65f8ab9814192e"}, 982 | {file = "psycopg2_binary-2.8.5-cp38-cp38-macosx_10_9_x86_64.macosx_10_9_intel.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:07cf82c870ec2d2ce94d18e70c13323c89f2f2a2628cbf1feee700630be2519a"}, 983 | {file = "psycopg2_binary-2.8.5-cp38-cp38-manylinux1_i686.whl", hash = "sha256:a69970ee896e21db4c57e398646af9edc71c003bc52a3cc77fb150240fefd266"}, 984 | {file = "psycopg2_binary-2.8.5-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7036ccf715925251fac969f4da9ad37e4b7e211b1e920860148a10c0de963522"}, 985 | {file = "psycopg2_binary-2.8.5-cp38-cp38-win32.whl", hash = "sha256:8f74e631b67482d504d7e9cf364071fc5d54c28e79a093ff402d5f8f81e23bfa"}, 986 | {file = "psycopg2_binary-2.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:fa466306fcf6b39b8a61d003123d442b23707d635a5cb05ac4e1b62cc79105cd"}, 987 | ] 988 | pycodestyle = [ 989 | {file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"}, 990 | {file = "pycodestyle-2.6.0.tar.gz", hash = "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e"}, 991 | ] 992 | pydantic = [ 993 | {file = "pydantic-1.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2a6904e9f18dea58f76f16b95cba6a2f20b72d787abd84ecd67ebc526e61dce6"}, 994 | {file = "pydantic-1.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da8099fca5ee339d5572cfa8af12cf0856ae993406f0b1eb9bb38c8a660e7416"}, 995 | {file = "pydantic-1.5.1-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:68dece67bff2b3a5cc188258e46b49f676a722304f1c6148ae08e9291e284d98"}, 996 | {file = "pydantic-1.5.1-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:ab863853cb502480b118187d670f753be65ec144e1654924bec33d63bc8b3ce2"}, 997 | {file = "pydantic-1.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:2007eb062ed0e57875ce8ead12760a6e44bf5836e6a1a7ea81d71eeecf3ede0f"}, 998 | {file = "pydantic-1.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:20a15a303ce1e4d831b4e79c17a4a29cb6740b12524f5bba3ea363bff65732bc"}, 999 | {file = "pydantic-1.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:473101121b1bd454c8effc9fe66d54812fdc128184d9015c5aaa0d4e58a6d338"}, 1000 | {file = "pydantic-1.5.1-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:9be755919258d5d168aeffbe913ed6e8bd562e018df7724b68cabdee3371e331"}, 1001 | {file = "pydantic-1.5.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:b96ce81c4b5ca62ab81181212edfd057beaa41411cd9700fbcb48a6ba6564b4e"}, 1002 | {file = "pydantic-1.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:93b9f265329d9827f39f0fca68f5d72cc8321881cdc519a1304fa73b9f8a75bd"}, 1003 | {file = "pydantic-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2c753d355126ddd1eefeb167fa61c7037ecd30b98e7ebecdc0d1da463b4ea09"}, 1004 | {file = "pydantic-1.5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:8433dbb87246c0f562af75d00fa80155b74e4f6924b0db6a2078a3cd2f11c6c4"}, 1005 | {file = "pydantic-1.5.1-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:0a1cdf24e567d42dc762d3fed399bd211a13db2e8462af9dfa93b34c41648efb"}, 1006 | {file = "pydantic-1.5.1-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:8be325fc9da897029ee48d1b5e40df817d97fe969f3ac3fd2434ba7e198c55d5"}, 1007 | {file = "pydantic-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:3714a4056f5bdbecf3a41e0706ec9b228c9513eee2ad884dc2c568c4dfa540e9"}, 1008 | {file = "pydantic-1.5.1-py36.py37.py38-none-any.whl", hash = "sha256:70f27d2f0268f490fe3de0a9b6fca7b7492b8fd6623f9fecd25b221ebee385e3"}, 1009 | {file = "pydantic-1.5.1.tar.gz", hash = "sha256:f0018613c7a0d19df3240c2a913849786f21b6539b9f23d85ce4067489dfacfa"}, 1010 | ] 1011 | pyflakes = [ 1012 | {file = "pyflakes-2.2.0-py2.py3-none-any.whl", hash = "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92"}, 1013 | {file = "pyflakes-2.2.0.tar.gz", hash = "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8"}, 1014 | ] 1015 | python-dateutil = [ 1016 | {file = "python-dateutil-2.8.1.tar.gz", hash = "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c"}, 1017 | {file = "python_dateutil-2.8.1-py2.py3-none-any.whl", hash = "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"}, 1018 | ] 1019 | python-editor = [ 1020 | {file = "python-editor-1.0.4.tar.gz", hash = "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b"}, 1021 | {file = "python_editor-1.0.4-py2-none-any.whl", hash = "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8"}, 1022 | {file = "python_editor-1.0.4-py2.7.egg", hash = "sha256:ea87e17f6ec459e780e4221f295411462e0d0810858e055fc514684350a2f522"}, 1023 | {file = "python_editor-1.0.4-py3-none-any.whl", hash = "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d"}, 1024 | {file = "python_editor-1.0.4-py3.5.egg", hash = "sha256:c3da2053dbab6b29c94e43c486ff67206eafbe7eb52dbec7390b5e2fb05aac77"}, 1025 | ] 1026 | pyyaml = [ 1027 | {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, 1028 | {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, 1029 | {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, 1030 | {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, 1031 | {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, 1032 | {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, 1033 | {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, 1034 | {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, 1035 | {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, 1036 | {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, 1037 | {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, 1038 | ] 1039 | regex = [ 1040 | {file = "regex-2020.5.7-cp27-cp27m-win32.whl", hash = "sha256:5493a02c1882d2acaaf17be81a3b65408ff541c922bfd002535c5f148aa29f74"}, 1041 | {file = "regex-2020.5.7-cp27-cp27m-win_amd64.whl", hash = "sha256:021a0ae4d2baeeb60a3014805a2096cb329bd6d9f30669b7ad0da51a9cb73349"}, 1042 | {file = "regex-2020.5.7-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4df91094ced6f53e71f695c909d9bad1cca8761d96fd9f23db12245b5521136e"}, 1043 | {file = "regex-2020.5.7-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7ce4a213a96d6c25eeae2f7d60d4dad89ac2b8134ec3e69db9bc522e2c0f9388"}, 1044 | {file = "regex-2020.5.7-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b059e2476b327b9794c792c855aa05531a3f3044737e455d283c7539bd7534d"}, 1045 | {file = "regex-2020.5.7-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:652ab4836cd5531d64a34403c00ada4077bb91112e8bcdae933e2eae232cf4a8"}, 1046 | {file = "regex-2020.5.7-cp36-cp36m-win32.whl", hash = "sha256:1e2255ae938a36e9bd7db3b93618796d90c07e5f64dd6a6750c55f51f8b76918"}, 1047 | {file = "regex-2020.5.7-cp36-cp36m-win_amd64.whl", hash = "sha256:8127ca2bf9539d6a64d03686fd9e789e8c194fc19af49b69b081f8c7e6ecb1bc"}, 1048 | {file = "regex-2020.5.7-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:f7f2f4226db6acd1da228adf433c5c3792858474e49d80668ea82ac87cf74a03"}, 1049 | {file = "regex-2020.5.7-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:2bc6a17a7fa8afd33c02d51b6f417fc271538990297167f68a98cae1c9e5c945"}, 1050 | {file = "regex-2020.5.7-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:b7c9f65524ff06bf70c945cd8d8d1fd90853e27ccf86026af2afb4d9a63d06b1"}, 1051 | {file = "regex-2020.5.7-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:fa09da4af4e5b15c0e8b4986a083f3fd159302ea115a6cc0649cd163435538b8"}, 1052 | {file = "regex-2020.5.7-cp37-cp37m-win32.whl", hash = "sha256:669a8d46764a09f198f2e91fc0d5acdac8e6b620376757a04682846ae28879c4"}, 1053 | {file = "regex-2020.5.7-cp37-cp37m-win_amd64.whl", hash = "sha256:b5b5b2e95f761a88d4c93691716ce01dc55f288a153face1654f868a8034f494"}, 1054 | {file = "regex-2020.5.7-cp38-cp38-manylinux1_i686.whl", hash = "sha256:0ff50843535593ee93acab662663cb2f52af8e31c3f525f630f1dc6156247938"}, 1055 | {file = "regex-2020.5.7-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1b17bf37c2aefc4cac8436971fe6ee52542ae4225cfc7762017f7e97a63ca998"}, 1056 | {file = "regex-2020.5.7-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:04d6e948ef34d3eac133bedc0098364a9e635a7914f050edb61272d2ddae3608"}, 1057 | {file = "regex-2020.5.7-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5b741ecc3ad3e463d2ba32dce512b412c319993c1bb3d999be49e6092a769fb2"}, 1058 | {file = "regex-2020.5.7-cp38-cp38-win32.whl", hash = "sha256:099568b372bda492be09c4f291b398475587d49937c659824f891182df728cdf"}, 1059 | {file = "regex-2020.5.7-cp38-cp38-win_amd64.whl", hash = "sha256:3ab5e41c4ed7cd4fa426c50add2892eb0f04ae4e73162155cd668257d02259dd"}, 1060 | {file = "regex-2020.5.7.tar.gz", hash = "sha256:73a10404867b835f1b8a64253e4621908f0d71150eb4e97ab2e7e441b53e9451"}, 1061 | ] 1062 | rope = [ 1063 | {file = "rope-0.17.0.tar.gz", hash = "sha256:658ad6705f43dcf3d6df379da9486529cf30e02d9ea14c5682aa80eb33b649e1"}, 1064 | ] 1065 | sentry-sdk = [ 1066 | {file = "sentry-sdk-0.14.3.tar.gz", hash = "sha256:bb90a4e19c7233a580715fc986cc44be2c48fc10b31e71580a2037e1c94b6950"}, 1067 | {file = "sentry_sdk-0.14.3-py2.py3-none-any.whl", hash = "sha256:23808d571d2461a4ce3784ec12bbee5bdb8c026c143fe79d36cef8a6d653e71f"}, 1068 | ] 1069 | six = [ 1070 | {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, 1071 | {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, 1072 | ] 1073 | smmap = [ 1074 | {file = "smmap-3.0.4-py2.py3-none-any.whl", hash = "sha256:54c44c197c819d5ef1991799a7e30b662d1e520f2ac75c9efbeb54a742214cf4"}, 1075 | {file = "smmap-3.0.4.tar.gz", hash = "sha256:9c98bbd1f9786d22f14b3d4126894d56befb835ec90cef151af566c7e19b5d24"}, 1076 | ] 1077 | sqlalchemy = [ 1078 | {file = "SQLAlchemy-1.3.16-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:8d8c21e9d4efef01351bf28513648ceb988031be4159745a7ad1b3e28c8ff68a"}, 1079 | {file = "SQLAlchemy-1.3.16-cp27-cp27m-win32.whl", hash = "sha256:083e383a1dca8384d0ea6378bd182d83c600ed4ff4ec8247d3b2442cf70db1ad"}, 1080 | {file = "SQLAlchemy-1.3.16-cp27-cp27m-win_amd64.whl", hash = "sha256:128f6179325f7597a46403dde0bf148478f868df44841348dfc8d158e00db1f9"}, 1081 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:6056b671aeda3fc451382e52ab8a753c0d5f66ef2a5ccc8fa5ba7abd20988b4d"}, 1082 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7025c639ce7e170db845e94006cf5f404e243e6fc00d6c86fa19e8ad8d411880"}, 1083 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e18752cecaef61031252ca72031d4d6247b3212ebb84748fc5d1a0d2029c23ea"}, 1084 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-win32.whl", hash = "sha256:0a690a6486658d03cc6a73536d46e796b6570ac1f8a7ec133f9e28c448b69828"}, 1085 | {file = "SQLAlchemy-1.3.16-cp36-cp36m-win_amd64.whl", hash = "sha256:d00b393f05dbd4ecd65c989b7f5a81110eae4baea7a6a4cdd94c20a908d1456e"}, 1086 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:114b6ace30001f056e944cebd46daef38fdb41ebb98f5e5940241a03ed6cad43"}, 1087 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:13d48cd8b925b6893a4e59b2dfb3e59a5204fd8c98289aad353af78bd214db49"}, 1088 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:211a1ce7e825f7142121144bac76f53ac28b12172716a710f4bf3eab477e730b"}, 1089 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-win32.whl", hash = "sha256:68d78cf4a9dfade2e6cf57c4be19f7b82ed66e67dacf93b32bb390c9bed12749"}, 1090 | {file = "SQLAlchemy-1.3.16-cp37-cp37m-win_amd64.whl", hash = "sha256:2dc57ee80b76813759cccd1a7affedf9c4dbe5b065a91fb6092c9d8151d66078"}, 1091 | {file = "SQLAlchemy-1.3.16-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:43078c7ec0457387c79b8d52fff90a7ad352ca4c7aa841c366238c3e2cf52fdf"}, 1092 | {file = "SQLAlchemy-1.3.16-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:bbb545da054e6297242a1bb1ba88e7a8ffb679f518258d66798ec712b82e4e07"}, 1093 | {file = "SQLAlchemy-1.3.16-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:5b1bf3c2c2dca738235ce08079783ef04f1a7fc5b21cf24adaae77f2da4e73c3"}, 1094 | {file = "SQLAlchemy-1.3.16-cp38-cp38-win32.whl", hash = "sha256:3e625e283eecc15aee5b1ef77203bfb542563fa4a9aa622c7643c7b55438ff49"}, 1095 | {file = "SQLAlchemy-1.3.16-cp38-cp38-win_amd64.whl", hash = "sha256:7d98e0785c4cd7ae30b4a451416db71f5724a1839025544b4edbd92e00b91f0f"}, 1096 | {file = "SQLAlchemy-1.3.16.tar.gz", hash = "sha256:7224e126c00b8178dfd227bc337ba5e754b197a3867d33b9f30dc0208f773d70"}, 1097 | ] 1098 | sqlalchemy-utils = [ 1099 | {file = "SQLAlchemy-Utils-0.36.5.tar.gz", hash = "sha256:680068c4b671225c183815e19b6f4adc765a9989dd5d9e8e9c900ede30cc7434"}, 1100 | ] 1101 | starlette = [ 1102 | {file = "starlette-0.13.2-py3-none-any.whl", hash = "sha256:6169ee78ded501095d1dda7b141a1dc9f9934d37ad23196e180150ace2c6449b"}, 1103 | {file = "starlette-0.13.2.tar.gz", hash = "sha256:a9bb130fa7aa736eda8a814b6ceb85ccf7a209ed53843d0d61e246b380afa10f"}, 1104 | ] 1105 | stevedore = [ 1106 | {file = "stevedore-1.32.0-py2.py3-none-any.whl", hash = "sha256:a4e7dc759fb0f2e3e2f7d8ffe2358c19d45b9b8297f393ef1256858d82f69c9b"}, 1107 | {file = "stevedore-1.32.0.tar.gz", hash = "sha256:18afaf1d623af5950cc0f7e75e70f917784c73b652a34a12d90b309451b5500b"}, 1108 | ] 1109 | taskipy = [ 1110 | {file = "taskipy-1.2.1-py3-none-any.whl", hash = "sha256:99bdaf5b19791c2345806847147e0fc2d28e1ac9446058def5a8b6b3fc9f23e2"}, 1111 | {file = "taskipy-1.2.1.tar.gz", hash = "sha256:5eb2c3b1606c896c7fa799848e71e8883b880759224958d07ba760e5db263175"}, 1112 | ] 1113 | toml = [ 1114 | {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, 1115 | {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, 1116 | {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, 1117 | ] 1118 | typed-ast = [ 1119 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, 1120 | {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, 1121 | {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, 1122 | {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, 1123 | {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, 1124 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, 1125 | {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, 1126 | {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, 1127 | {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, 1128 | {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, 1129 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, 1130 | {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, 1131 | {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, 1132 | {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, 1133 | {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, 1134 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, 1135 | {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, 1136 | {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, 1137 | {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, 1138 | {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, 1139 | {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, 1140 | ] 1141 | urllib3 = [ 1142 | {file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"}, 1143 | {file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"}, 1144 | ] 1145 | uvicorn = [ 1146 | {file = "uvicorn-0.11.5-py3-none-any.whl", hash = "sha256:50577d599775dac2301bac8bd5b540d19a9560144143c5bdab13cba92783b6e7"}, 1147 | {file = "uvicorn-0.11.5.tar.gz", hash = "sha256:596eaa8645b6dbc24d6610e335f8ddf5f925b4c4b86fdc7146abb0bf0da65d17"}, 1148 | ] 1149 | uvloop = [ 1150 | {file = "uvloop-0.14.0-cp35-cp35m-macosx_10_11_x86_64.whl", hash = "sha256:08b109f0213af392150e2fe6f81d33261bb5ce968a288eb698aad4f46eb711bd"}, 1151 | {file = "uvloop-0.14.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:4544dcf77d74f3a84f03dd6278174575c44c67d7165d4c42c71db3fdc3860726"}, 1152 | {file = "uvloop-0.14.0-cp36-cp36m-macosx_10_11_x86_64.whl", hash = "sha256:b4f591aa4b3fa7f32fb51e2ee9fea1b495eb75b0b3c8d0ca52514ad675ae63f7"}, 1153 | {file = "uvloop-0.14.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:f07909cd9fc08c52d294b1570bba92186181ca01fe3dc9ffba68955273dd7362"}, 1154 | {file = "uvloop-0.14.0-cp37-cp37m-macosx_10_11_x86_64.whl", hash = "sha256:afd5513c0ae414ec71d24f6f123614a80f3d27ca655a4fcf6cabe50994cc1891"}, 1155 | {file = "uvloop-0.14.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e7514d7a48c063226b7d06617cbb12a14278d4323a065a8d46a7962686ce2e95"}, 1156 | {file = "uvloop-0.14.0-cp38-cp38-macosx_10_11_x86_64.whl", hash = "sha256:bcac356d62edd330080aed082e78d4b580ff260a677508718f88016333e2c9c5"}, 1157 | {file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"}, 1158 | {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"}, 1159 | ] 1160 | virtualenv = [ 1161 | {file = "virtualenv-20.0.20-py2.py3-none-any.whl", hash = "sha256:b4c14d4d73a0c23db267095383c4276ef60e161f94fde0427f2f21a0132dde74"}, 1162 | {file = "virtualenv-20.0.20.tar.gz", hash = "sha256:fd0e54dec8ac96c1c7c87daba85f0a59a7c37fe38748e154306ca21c73244637"}, 1163 | ] 1164 | watchgod = [ 1165 | {file = "watchgod-0.6-py35.py36.py37-none-any.whl", hash = "sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a"}, 1166 | {file = "watchgod-0.6.tar.gz", hash = "sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858"}, 1167 | ] 1168 | websockets = [ 1169 | {file = "websockets-8.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:3762791ab8b38948f0c4d281c8b2ddfa99b7e510e46bd8dfa942a5fff621068c"}, 1170 | {file = "websockets-8.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:3db87421956f1b0779a7564915875ba774295cc86e81bc671631379371af1170"}, 1171 | {file = "websockets-8.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4f9f7d28ce1d8f1295717c2c25b732c2bc0645db3215cf757551c392177d7cb8"}, 1172 | {file = "websockets-8.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:295359a2cc78736737dd88c343cd0747546b2174b5e1adc223824bcaf3e164cb"}, 1173 | {file = "websockets-8.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1d3f1bf059d04a4e0eb4985a887d49195e15ebabc42364f4eb564b1d065793f5"}, 1174 | {file = "websockets-8.1-cp36-cp36m-win32.whl", hash = "sha256:2db62a9142e88535038a6bcfea70ef9447696ea77891aebb730a333a51ed559a"}, 1175 | {file = "websockets-8.1-cp36-cp36m-win_amd64.whl", hash = "sha256:0e4fb4de42701340bd2353bb2eee45314651caa6ccee80dbd5f5d5978888fed5"}, 1176 | {file = "websockets-8.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:9b248ba3dd8a03b1a10b19efe7d4f7fa41d158fdaa95e2cf65af5a7b95a4f989"}, 1177 | {file = "websockets-8.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:ce85b06a10fc65e6143518b96d3dca27b081a740bae261c2fb20375801a9d56d"}, 1178 | {file = "websockets-8.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:965889d9f0e2a75edd81a07592d0ced54daa5b0785f57dc429c378edbcffe779"}, 1179 | {file = "websockets-8.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:751a556205d8245ff94aeef23546a1113b1dd4f6e4d102ded66c39b99c2ce6c8"}, 1180 | {file = "websockets-8.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:3ef56fcc7b1ff90de46ccd5a687bbd13a3180132268c4254fc0fa44ecf4fc422"}, 1181 | {file = "websockets-8.1-cp37-cp37m-win32.whl", hash = "sha256:7ff46d441db78241f4c6c27b3868c9ae71473fe03341340d2dfdbe8d79310acc"}, 1182 | {file = "websockets-8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:20891f0dddade307ffddf593c733a3fdb6b83e6f9eef85908113e628fa5a8308"}, 1183 | {file = "websockets-8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c1ec8db4fac31850286b7cd3b9c0e1b944204668b8eb721674916d4e28744092"}, 1184 | {file = "websockets-8.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:5c01fd846263a75bc8a2b9542606927cfad57e7282965d96b93c387622487485"}, 1185 | {file = "websockets-8.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:9bef37ee224e104a413f0780e29adb3e514a5b698aabe0d969a6ba426b8435d1"}, 1186 | {file = "websockets-8.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:d705f8aeecdf3262379644e4b55107a3b55860eb812b673b28d0fbc347a60c55"}, 1187 | {file = "websockets-8.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:c8a116feafdb1f84607cb3b14aa1418424ae71fee131642fc568d21423b51824"}, 1188 | {file = "websockets-8.1-cp38-cp38-win32.whl", hash = "sha256:e898a0863421650f0bebac8ba40840fc02258ef4714cb7e1fd76b6a6354bda36"}, 1189 | {file = "websockets-8.1-cp38-cp38-win_amd64.whl", hash = "sha256:f8a7bff6e8664afc4e6c28b983845c5bc14965030e3fb98789734d416af77c4b"}, 1190 | {file = "websockets-8.1.tar.gz", hash = "sha256:5c65d2da8c6bce0fca2528f69f44b2f977e06954c8512a952222cea50dad430f"}, 1191 | ] 1192 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fastapi-gino-arq-uvicorn" 3 | version = "0.4.0" 4 | description = "High-performance Async REST API, in Python. FastAPI + GINO + Arq + Uvicorn (w/ Redis and PostgreSQL)." 5 | authors = ["Leo Sussan "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.8" 9 | fastapi = "^0.54.1" 10 | gino = "^1.0.0" 11 | arq = "^0.19" 12 | sqlalchemy-utils = "^0.36.5" 13 | alembic = "^1.4.2" 14 | psycopg2-binary = "^2.8.5" 15 | uvicorn = "^0.11.5" 16 | email_validator = "^1.1.0" 17 | sentry-sdk = "^0.14.3" 18 | gino-starlette = "^0.1.1" 19 | 20 | [tool.poetry.dev-dependencies] 21 | black = "^19.10b0" 22 | bandit = "^1.6.2" 23 | flake8 = "^3.8.1" 24 | rope = "^0.17.0" 25 | watchgod = "^0.6" 26 | taskipy = "^1.2.1" 27 | pre-commit = "^2.4.0" 28 | isort = {version = "^4.3.21", extras = ["pyproject"]} 29 | 30 | [tool.taskipy.tasks] 31 | # Launch Commands for Local Development 32 | app = "uvicorn app.main:app --reload" 33 | worker = "arq app.worker.WorkerSettings --watch ./" 34 | # Docker-Compose Tasks 35 | compose-up = "docker-compose -f docker-compose.local.yml -f docker-compose.worker.yml -f docker-compose.yml up --build" 36 | compose-down = "docker-compose -f docker-compose.local.yml -f docker-compose.worker.yml -f docker-compose.yml down --remove-orphans" 37 | 38 | [tool.black] 39 | line-length = 79 40 | target-version = ['py38'] 41 | exclude = ''' 42 | /( 43 | \.git 44 | | .vscode 45 | | build 46 | | dist 47 | | .scripts 48 | | .git-crypt 49 | | services/.scripts 50 | | services/.vscode 51 | | core/.scripts 52 | | core/.vscode 53 | )/ 54 | ''' 55 | 56 | [tool.isort] 57 | line_length = 79 58 | multi_line_output = 3 59 | not_skip = "__init__.py" 60 | use_parentheses = true 61 | include_trailing_comma = true 62 | force_grid_wrap = 0 63 | known_third_party = ["alembic", "arq", "fastapi", "gino", "pydantic", "sentry_sdk", "sqlalchemy", "sqlalchemy_utils", "starlette"] --------------------------------------------------------------------------------