diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..c0720ec --- /dev/null +++ b/.flake8 @@ -0,0 +1,9 @@ +[flake8] +ignore = E203, E266, E501, W503, F403, F401 +max-line-length = 79 +max-complexity = 18 +select = B,C,E,F,W,T4,B9 +per-file-ignores = + app/models/orm/migrations/env.py:E402 + app/main.py:E402 + app/worker.py:E402 \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ec617b8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,25 @@ +repos: + - repo: https://github.com/asottile/seed-isort-config + rev: v2.1.1 + hooks: + - id: seed-isort-config + args: [--application-directories, "./app:./"] + - repo: https://github.com/pre-commit/mirrors-isort + rev: v4.3.21 + hooks: + - id: isort + additional_dependencies: ["toml"] + - repo: https://github.com/ambv/black + rev: stable + hooks: + - id: black + - repo: https://gitlab.com/pycqa/flake8 + rev: 3.7.9 + hooks: + - id: flake8 + additional_dependencies: [ + "git+https://github.com/PyCQA/pyflakes#egg=pyflakes", + "git+https://gitlab.com/PyCQA/pycodestyle#egg=pycodestyle", + ] +default_language_version: + python: python3.8 \ No newline at end of file diff --git a/README.md b/README.md index d0c534f..37070c2 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,52 @@ # fastapi-gino-arq-uvicorn High-performance Async REST API, in Python. FastAPI + GINO + Arq + Uvicorn (powered by Redis & PostgreSQL). -## Get Started -### Run Locally -_NOTE: You must have PostgreSQL & Redis running locally._ +## Contents +* [Get Started](#get-started) + * [Setup](#setup) + * [Run](#run) + * [Run Locally](#run-locally) + * [Run Locally with Docker-Compose](#run-locally-with-docker-compose) + * [Build Your Application](#build-your-application) +* [Features](#features) + * [Core Dependencies](#core-dependencies) + * [Additional Dependencies](#additional-dependencies) +## Get Started +### Setup 1. Clone this Repository. `git clone https://github.com/leosussan/fastapi-gino-arq-uvicorn.git` 2. Install `Python 3.8` and `poetry`. - * _RECOMMENDED_: use `asdf`, which is like `pyenv` / `nvm` / `gvm` for everything. - * [Install](https://asdf-vm.com/#/core-manage-asdf-vm?id=install-asdf-vm), then run `asdf plugin add python`, `asdf plugin add poetry`, and `asdf install` -3. Run `poetry install` from root. -4. Make a copy of `.dist.env`, rename to `.env`. Fill in PostgreSQL, Redis connection vars. -5. Generate DB Migrations: `alembic revision --autogenerate`. It will be applied when the application starts. You can trigger manually with `alembic upgrade head`. -6. Run: + * Recommended Method: `asdf` - a universal version manager (think `nvm` or `pyenv`) + * Follow [these instructions](https://asdf-vm.com/#/core-manage-asdf-vm?id=install-asdf-vm) to install `asdf`. + * Run the following commands from the project root: + * `asdf plugin add python` + * `asdf plugin add poetry` + * `asdf install` -- will download & configure this project's `Python` + `poetry` setup + * If you have `Python 3.8` and `poetry` installed already, please feel free to skip. +3. Install dependencies (`poetry install`). +4. Activate pre-commit hooks (in `poetry shell`, run `pre-commit install`). +5. Make a copy of `.dist.env`, rename to `.env`. Fill in PostgreSQL, Redis, Sentry (optional) variables. +6. Generate DB Migrations: in `poetry shell`, run `alembic revision --autogenerate`. + * Apply migrations manually with `alembic upgrade head`. + * If using the Dockerfile, migrations are applied at startup. + +### Run + +#### Run Locally +_NOTE: You must have PostgreSQL & Redis running locally._ + +1. Make sure PostgreSQL & Redis are running locally. +2. Run: - FastAPI Application: * _For Active Development (w/ auto-reload):_ Run locally with `poetry run task app` * _For Debugging (compatible w/ debuggers, no auto-reload):_ Configure debugger to run `python app/main.py`. - Background Task Worker: * _For Active Development:_ Run `poetry run task worker` -### Run Locally with Docker-Compose -1. Clone this Repository. `git clone https://github.com/leosussan/fastapi-gino-arq-uvicorn.git` -2. Generate a DB Migration: `alembic revision --autogenerate`.* -3. Run locally using docker-compose. `poetry run task compose-up`. - * Run `poetry run task compose-down` to spin down, clean up. +#### Run Locally with Docker-Compose. +1. Make sure `Docker` is running locally. +2. Run `poetry run task compose-up`*. + - Run `poetry run task compose-down` to spin down, clean up. *`app/settings/prestart.sh` will run migrations for you before the app starts. @@ -53,4 +76,5 @@ _NOTE: You must have PostgreSQL & Redis running locally._ * **Alembic:** Handles database migrations. Compatible with GINO. * **SQLAlchemy_Utils:** Provides essential handles & datatypes. Compatible with GINO. * **Sentry:** Open-source, cloud-hosted error + event monitoring. +* **Pre-Commit:** automatic formatting (`black` + `isort`) and linting (`flake8`). * **Taskipy:** Small, flexible task runner for Poetry. diff --git a/app/application.py b/app/application.py index 327cc50..fda12c6 100644 --- a/app/application.py +++ b/app/application.py @@ -2,15 +2,14 @@ from gino.ext.starlette import Gino from sentry_sdk import init as initialize_sentry from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration -from starlette.datastructures import Secret from sqlalchemy.schema import MetaData +from starlette.datastructures import Secret from .settings.globals import DATABASE_CONFIG, SENTRY_DSN if isinstance(SENTRY_DSN, Secret) and SENTRY_DSN.__str__() not in ("None", ""): initialize_sentry( - dsn=SENTRY_DSN.__str__(), - integrations=[SqlalchemyIntegration()] + dsn=SENTRY_DSN.__str__(), integrations=[SqlalchemyIntegration()] ) app: FastAPI = FastAPI() diff --git a/app/main.py b/app/main.py index 9d1cc96..fbe6878 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,5 @@ +# isort:skip_file + import sys sys.path.extend(["./"]) diff --git a/app/models/orm/base.py b/app/models/orm/base.py index 0a89d11..a738ac8 100644 --- a/app/models/orm/base.py +++ b/app/models/orm/base.py @@ -11,7 +11,13 @@ @generic_repr class Base(db.Model): __abstract__ = True - created_on = db.Column(db.DateTime, default=datetime.utcnow, server_default=db.func.now()) + created_on = db.Column( + db.DateTime, default=datetime.utcnow, server_default=db.func.now() + ) updated_on = db.Column( - db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, server_default=db.func.now()) + db.DateTime, + default=datetime.utcnow, + onupdate=datetime.utcnow, + server_default=db.func.now(), + ) id = db.Column(db.BigInteger, primary_key=True, autoincrement=True) diff --git a/app/models/orm/migrations/env.py b/app/models/orm/migrations/env.py index 69d6e9e..e5bcb4b 100644 --- a/app/models/orm/migrations/env.py +++ b/app/models/orm/migrations/env.py @@ -1,23 +1,25 @@ -# Native libraries +# isort:skip_file + import sys -sys.path.extend(['./']) + +sys.path.extend(["./"]) + +from logging.config import fileConfig + +from sqlalchemy import engine_from_config, pool +from alembic import context + +from app.settings.globals import ALEMBIC_CONFIG ######################## --- MODELS FOR MIGRATIONS --- ######################## from app.application import db from app.models.orm.user import User # To include a model in migrations, add a line here. +# from app.models.orm.person import Person ############################################################################### -# Third party packages -from sqlalchemy import engine_from_config, pool -from alembic import context -from logging.config import fileConfig - -# App imports -from app.settings.globals import ALEMBIC_CONFIG - config = context.config fileConfig(config.config_file_name) target_metadata = db @@ -36,7 +38,9 @@ def run_migrations_offline(): """ context.configure( - url=ALEMBIC_CONFIG.url.__to_string__(hide_password=False), target_metadata=target_metadata, literal_binds=True + url=ALEMBIC_CONFIG.url.__to_string__(hide_password=False), + target_metadata=target_metadata, + literal_binds=True, ) with context.begin_transaction(): @@ -51,7 +55,11 @@ def run_migrations_online(): """ connectable = engine_from_config( - {"sqlalchemy.url": ALEMBIC_CONFIG.url.__to_string__(hide_password=False)}, + { + "sqlalchemy.url": ALEMBIC_CONFIG.url.__to_string__( + hide_password=False + ) + }, prefix="sqlalchemy.", poolclass=pool.NullPool, ) diff --git a/app/models/orm/user.py b/app/models/orm/user.py index 1a8d3c0..2d2a2c2 100644 --- a/app/models/orm/user.py +++ b/app/models/orm/user.py @@ -2,7 +2,7 @@ class User(Base): - __tablename__ = 'users' + __tablename__ = "users" name = db.Column(db.String(255)) email = db.Column(db.EmailType) phone_number = db.Column(db.Unicode(20)) diff --git a/app/models/pydantic/base.py b/app/models/pydantic/base.py index 3db4e0e..5e699aa 100644 --- a/app/models/pydantic/base.py +++ b/app/models/pydantic/base.py @@ -1,6 +1,7 @@ -from pydantic import BaseModel from datetime import datetime +from pydantic import BaseModel + class Base(BaseModel): id: int diff --git a/app/models/pydantic/user.py b/app/models/pydantic/user.py index 400c0fe..31d02e4 100644 --- a/app/models/pydantic/user.py +++ b/app/models/pydantic/user.py @@ -1,6 +1,7 @@ -from pydantic import EmailStr, BaseModel from typing import Optional +from pydantic import BaseModel, EmailStr + from .base import Base diff --git a/app/routes/users.py b/app/routes/users.py index 975cf61..6ccec46 100644 --- a/app/routes/users.py +++ b/app/routes/users.py @@ -1,4 +1,4 @@ -from arq.connections import create_pool, ArqRedis +from arq.connections import ArqRedis, create_pool from fastapi import APIRouter from ..models.orm.user import User as ORMUser diff --git a/app/settings/globals.py b/app/settings/globals.py index 264572b..ae2db57 100644 --- a/app/settings/globals.py +++ b/app/settings/globals.py @@ -2,7 +2,7 @@ from typing import Optional from starlette.config import Config -from starlette.datastructures import Secret, CommaSeparatedStrings +from starlette.datastructures import CommaSeparatedStrings, Secret from ..models.pydantic.database import DatabaseURL diff --git a/app/tasks/messaging.py b/app/tasks/messaging.py index 3067cb8..85bb628 100644 --- a/app/tasks/messaging.py +++ b/app/tasks/messaging.py @@ -1,6 +1,6 @@ from ..application import db -from ..models.pydantic.user import User from ..models.orm.user import User as ORMUser +from ..models.pydantic.user import User async def send_message(ctx: dict, user_id: int, message: str): diff --git a/app/worker.py b/app/worker.py index 936588d..d0df82a 100644 --- a/app/worker.py +++ b/app/worker.py @@ -1,9 +1,14 @@ +# isort:skip_file + +import sys + +sys.path.extend(["./"]) + from pydantic.utils import import_string from .application import db from .settings.arq import settings -from .settings.globals import DATABASE_CONFIG, ARQ_BACKGROUND_FUNCTIONS - +from .settings.globals import ARQ_BACKGROUND_FUNCTIONS, DATABASE_CONFIG FUNCTIONS: list = [ import_string(background_function) diff --git a/poetry.lock b/poetry.lock index b819dd4..233bd96 100644 --- a/poetry.lock +++ b/poetry.lock @@ -127,6 +127,14 @@ optional = false python-versions = "*" version = "2020.4.5.1" +[[package]] +category = "dev" +description = "Validate configuration and produce human readable error messages." +name = "cfgv" +optional = false +python-versions = ">=3.6.1" +version = "3.1.0" + [[package]] category = "main" description = "Composable command line interface toolkit" @@ -144,6 +152,14 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.4.3" +[[package]] +category = "dev" +description = "Distribution utilities" +name = "distlib" +optional = false +python-versions = "*" +version = "0.3.0" + [[package]] category = "main" description = "DNS toolkit" @@ -186,6 +202,14 @@ dev = ["pyjwt", "passlib", "autoflake", "flake8", "uvicorn", "graphene"] doc = ["mkdocs", "mkdocs-material", "markdown-include", "typer", "typer-cli", "pyyaml"] 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"] +[[package]] +category = "dev" +description = "A platform independent file lock." +name = "filelock" +optional = false +python-versions = "*" +version = "3.0.12" + [[package]] category = "dev" description = "the modular source code checker: pep8 pyflakes and co" @@ -280,6 +304,17 @@ version = "0.1.1" [package.extras] test = ["Cython (0.29.14)"] +[[package]] +category = "dev" +description = "File identification library for Python" +name = "identify" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "1.4.15" + +[package.extras] +license = ["editdistance"] + [[package]] category = "main" description = "Internationalized Domain Names in Applications (IDNA)" @@ -288,6 +323,25 @@ optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.9" +[[package]] +category = "dev" +description = "A Python utility / library to sort Python imports." +name = "isort" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +version = "4.3.21" + +[package.dependencies] +[package.dependencies.toml] +optional = true +version = "*" + +[package.extras] +pipfile = ["pipreqs", "requirementslib"] +pyproject = ["toml"] +requirements = ["pipreqs", "pip-api"] +xdg_home = ["appdirs (>=1.4.0)"] + [[package]] category = "main" description = "A super-fast templating language that borrows the best ideas from the existing templating languages." @@ -319,6 +373,14 @@ optional = false python-versions = "*" version = "0.6.1" +[[package]] +category = "dev" +description = "Node.js virtual environment builder" +name = "nodeenv" +optional = false +python-versions = "*" +version = "1.3.5" + [[package]] category = "dev" description = "Utility library for gitignore style pattern matching of file paths." @@ -335,6 +397,22 @@ optional = false python-versions = "*" version = "5.4.5" +[[package]] +category = "dev" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +name = "pre-commit" +optional = false +python-versions = ">=3.6.1" +version = "2.4.0" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + [[package]] category = "main" description = "psycopg2 - Python-PostgreSQL Database Adapter" @@ -597,6 +675,24 @@ optional = false python-versions = "*" version = "0.14.0" +[[package]] +category = "dev" +description = "Virtual Python Environment builder" +name = "virtualenv" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +version = "20.0.20" + +[package.dependencies] +appdirs = ">=1.4.3,<2" +distlib = ">=0.3.0,<1" +filelock = ">=3.0.0,<4" +six = ">=1.9.0,<2" + +[package.extras] +docs = ["sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=19.9.0rc1)", "proselint (>=0.10.2)"] +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)"] + [[package]] category = "dev" description = "Simple, modern file watching and code reload in python." @@ -614,7 +710,7 @@ python-versions = ">=3.6.1" version = "8.1" [metadata] -content-hash = "3884ddb78af0f8cfa5da6c7dfbfbf057e4fbf042dba07409184e10c1358db772" +content-hash = "3dd14972fac519cde9117da78ec8a1f9701420a5549833396ec1dbc0b350f63b" python-versions = "^3.8" [metadata.files] @@ -676,6 +772,10 @@ certifi = [ {file = "certifi-2020.4.5.1-py2.py3-none-any.whl", hash = "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304"}, {file = "certifi-2020.4.5.1.tar.gz", hash = "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"}, ] +cfgv = [ + {file = "cfgv-3.1.0-py2.py3-none-any.whl", hash = "sha256:1ccf53320421aeeb915275a196e23b3b8ae87dea8ac6698b1638001d4a486d53"}, + {file = "cfgv-3.1.0.tar.gz", hash = "sha256:c8e8f552ffcc6194f4e18dd4f68d9aef0c0d58ae7e7be8c82bee3c5e9edfa513"}, +] click = [ {file = "click-7.1.2-py2.py3-none-any.whl", hash = "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"}, {file = "click-7.1.2.tar.gz", hash = "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a"}, @@ -684,6 +784,9 @@ colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] +distlib = [ + {file = "distlib-0.3.0.zip", hash = "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21"}, +] dnspython = [ {file = "dnspython-1.16.0-py2.py3-none-any.whl", hash = "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d"}, {file = "dnspython-1.16.0.zip", hash = "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01"}, @@ -696,6 +799,10 @@ fastapi = [ {file = "fastapi-0.54.1-py3-none-any.whl", hash = "sha256:1ee9a49f28d510b62b3b51a9452b274853bfc9c5d4b947ed054366e2d49f9efa"}, {file = "fastapi-0.54.1.tar.gz", hash = "sha256:72f40f47e5235cb5cbbad1d4f97932ede6059290c07e12e9784028dcd1063d28"}, ] +filelock = [ + {file = "filelock-3.0.12-py3-none-any.whl", hash = "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836"}, + {file = "filelock-3.0.12.tar.gz", hash = "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59"}, +] flake8 = [ {file = "flake8-3.8.1-py2.py3-none-any.whl", hash = "sha256:6c1193b0c3f853ef763969238f6c81e9e63ace9d024518edc020d5f1d6d93195"}, {file = "flake8-3.8.1.tar.gz", hash = "sha256:ea6623797bf9a52f4c9577d780da0bb17d65f870213f7b5bcc9fca82540c31d5"}, @@ -776,10 +883,18 @@ httptools = [ {file = "httptools-0.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:0a4b1b2012b28e68306575ad14ad5e9120b34fccd02a81eb08838d7e3bbb48be"}, {file = "httptools-0.1.1.tar.gz", hash = "sha256:41b573cf33f64a8f8f3400d0a7faf48e1888582b6f6e02b82b9bd4f0bf7497ce"}, ] +identify = [ + {file = "identify-1.4.15-py2.py3-none-any.whl", hash = "sha256:88ed90632023e52a6495749c6732e61e08ec9f4f04e95484a5c37b9caf40283c"}, + {file = "identify-1.4.15.tar.gz", hash = "sha256:23c18d97bb50e05be1a54917ee45cc61d57cb96aedc06aabb2b02331edf0dbf0"}, +] idna = [ {file = "idna-2.9-py2.py3-none-any.whl", hash = "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"}, {file = "idna-2.9.tar.gz", hash = "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb"}, ] +isort = [ + {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, + {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, +] mako = [ {file = "Mako-1.1.2-py2.py3-none-any.whl", hash = "sha256:8e8b53c71c7e59f3de716b6832c4e401d903af574f6962edbbbf6ecc2a5fe6c9"}, {file = "Mako-1.1.2.tar.gz", hash = "sha256:3139c5d64aa5d175dbafb95027057128b5fbd05a40c53999f3905ceb53366d9d"}, @@ -823,6 +938,9 @@ mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] +nodeenv = [ + {file = "nodeenv-1.3.5-py2.py3-none-any.whl", hash = "sha256:5b2438f2e42af54ca968dd1b374d14a1194848955187b0e5e4be1f73813a5212"}, +] pathspec = [ {file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"}, {file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"}, @@ -831,6 +949,10 @@ pbr = [ {file = "pbr-5.4.5-py2.py3-none-any.whl", hash = "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8"}, {file = "pbr-5.4.5.tar.gz", hash = "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c"}, ] +pre-commit = [ + {file = "pre_commit-2.4.0-py2.py3-none-any.whl", hash = "sha256:5559e09afcac7808933951ffaf4ff9aac524f31efbc3f24d021540b6c579813c"}, + {file = "pre_commit-2.4.0.tar.gz", hash = "sha256:703e2e34cbe0eedb0d319eff9f7b83e2022bb5a3ab5289a6a8841441076514d0"}, +] psycopg2-binary = [ {file = "psycopg2-binary-2.8.5.tar.gz", hash = "sha256:ccdc6a87f32b491129ada4b87a43b1895cf2c20fdb7f98ad979647506ffc41b6"}, {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"}, @@ -1035,6 +1157,10 @@ uvloop = [ {file = "uvloop-0.14.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4315d2ec3ca393dd5bc0b0089d23101276778c304d42faff5dc4579cb6caef09"}, {file = "uvloop-0.14.0.tar.gz", hash = "sha256:123ac9c0c7dd71464f58f1b4ee0bbd81285d96cdda8bc3519281b8973e3a461e"}, ] +virtualenv = [ + {file = "virtualenv-20.0.20-py2.py3-none-any.whl", hash = "sha256:b4c14d4d73a0c23db267095383c4276ef60e161f94fde0427f2f21a0132dde74"}, + {file = "virtualenv-20.0.20.tar.gz", hash = "sha256:fd0e54dec8ac96c1c7c87daba85f0a59a7c37fe38748e154306ca21c73244637"}, +] watchgod = [ {file = "watchgod-0.6-py35.py36.py37-none-any.whl", hash = "sha256:59700dab7445aa8e6067a5b94f37bae90fc367554549b1ed2e9d0f4f38a90d2a"}, {file = "watchgod-0.6.tar.gz", hash = "sha256:e9cca0ab9c63f17fc85df9fd8bd18156ff00aff04ebe5976cee473f4968c6858"}, diff --git a/pyproject.toml b/pyproject.toml index dae7eec..1d0dd17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,8 @@ flake8 = "^3.8.1" rope = "^0.17.0" watchgod = "^0.6" taskipy = "^1.2.1" +pre-commit = "^2.4.0" +isort = {version = "^4.3.21", extras = ["pyproject"]} [tool.taskipy.tasks] # Launch Commands for Local Development @@ -31,4 +33,31 @@ app = "uvicorn app.main:app --reload" worker = "arq app.worker.WorkerSettings --watch ./" # Docker-Compose Tasks compose-up = "docker-compose -f docker-compose.local.yml -f docker-compose.worker.yml -f docker-compose.yml up --build" -compose-down = "docker-compose -f docker-compose.local.yml -f docker-compose.worker.yml -f docker-compose.yml down --remove-orphans" \ No newline at end of file +compose-down = "docker-compose -f docker-compose.local.yml -f docker-compose.worker.yml -f docker-compose.yml down --remove-orphans" + +[tool.black] +line-length = 79 +target-version = ['py38'] +exclude = ''' +/( + \.git + | .vscode + | build + | dist + | .scripts + | .git-crypt + | services/.scripts + | services/.vscode + | core/.scripts + | core/.vscode +)/ +''' + +[tool.isort] +line_length = 79 +multi_line_output = 3 +not_skip = "__init__.py" +use_parentheses = true +include_trailing_comma = true +force_grid_wrap = 0 +known_third_party = ["alembic", "arq", "fastapi", "gino", "pydantic", "sentry_sdk", "sqlalchemy", "sqlalchemy_utils", "starlette"] \ No newline at end of file