diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..600d2d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index ba8c3b2..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "python.formatting.provider": "black", - "python.analysis.typeCheckingMode": "basic", -} \ No newline at end of file diff --git a/README.md b/README.md index 33560cb..b9bef26 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,29 @@ -# Full featured FastAPI template, all boring and tedious things are covered +## Minimal async FastAPI + postgresql template + +![OpenAPIexample](./docs/OpenAPI_example.png) - SQLAlchemy using new 2.0 API + async queries - Postgresql database under `asyncpg` - Alembic migrations - Very minimal project structure yet ready for quick start building new api - Refresh token endpoint (not only access like in official template) -- Two databases in docker-compose.yml (second for tests) +- Two databases in docker-compose.yml (second one for tests) - poetry - `pre-push.sh` script with poetry export, autoflake, black, isort and flake8 -- Setup for tests, one big test for token flow and very extensible `conftest.py` +- Setup for async tests, one func test for token flow and very extensible `conftest.py` + +## What this repo is + +This is a minimal template for FastAPI backend + postgresql db as of 2021.11, `async` style for database sessions, endpoints and tests. It provides basic codebase that almost every application has, but nothing more. + +## What this repo is not -# Quickstart +It is not complex, full featured solutions for all human kind problems. It doesn't include any third party that isn't necessary for most of apps (dashboards, queues) or implementation differs so much in every project that it's pointless (complex User model, emails, RBAC, permissions). + +## Quickstart ```bash -# You can install it globally +# Install cookiecutter globally pip install cookiecutter # And cookiecutter this project :) @@ -22,24 +32,184 @@ cookiecutter https://github.com/rafsaf/minimal-fastapi-postgres-template cd project_name # Poetry install (and activate environment!) poetry install -# Databases +# Setup two databases docker-compose up -d # Alembic migrations upgrade and initial_data.py script bash init.sh # And this is it: -uvicorn app.main:app +uvicorn app.main:app --reload ``` tests: ```bash +# Note, it will use second database declared in docker-compose.yml, not default one pytest -# Note, it will use second database declared in docker-compose.yml, not default one like -# in official template + ``` -# About +## About This project is heavily base on official template https://github.com/tiangolo/full-stack-fastapi-postgresql (and on my previous work: [link1](https://github.com/rafsaf/fastapi-plan), [link2](https://github.com/rafsaf/docker-fastapi-projects)), but as it is now not too much up-to-date, it is much easier to create new one than change official. I didn't like some of conventions over there also (`crud` and `db` folders for example). -`2.0` style SQLAlchemy API is good enough so there is no need to write everything in `crud` and waste our time... The `core` folder was also rewritten. There is great base for writting tests in `tests`, but I didn't want to write hundreds of them, I noticed that usually after changes in the structure of the project, auto tests are useless and you have to write them from scratch, hence less than more. Similarly with the `User` model, it is very modest, because it will be adapted to the project anyway (and there are no tests for these endpoints) +`2.0` style SQLAlchemy API is good enough so there is no need to write everything in `crud` and waste our time... The `core` folder was also rewritten. There is great base for writting tests in `tests`, but I didn't want to write hundreds of them, I noticed that usually after changes in the structure of the project, auto tests are useless and you have to write them from scratch anyway (delete old ones...), hence less than more. Similarly with the `User` model, it is very modest, because it will be adapted to the project anyway (and there are no tests for these endpoints, you would remove them probably). + +## Step by step example + +I always enjoy to to have some kind of example in templates (even if I don't like it much, _some_ parts may be useful and save my time...), so let's create `POST` endpoint for creating dogs. + +### 1. Add `HappyDog` model + +```python +# /app/models.py +(...) + +class HappyDog(Base): + __tablename__ = "happy_dog" + id = Column(Integer, primary_key=True, index=True) + puppy_name = Column(String(500)) + puppy_age = Column(Integer) +``` + +### 2. Create and apply alembic migrations + +```bash +# Run +alembic revision --autogenerate -m "add_happy_dog" + +# Somethig like `YYYY-MM-DD-....py` will appear in `/alembic/versions` folder + +alembic upgrade head + +# (...) +# INFO [alembic.runtime.migration] Running upgrade cefce371682e -> 038f530b0e9b, add_happy_dog +``` + +PS. Note, alembic is configured in a way that it work with async setup and also detects specific column changes. + +### 3. Create schemas + +```python +# /app/schemas/happy_dog.py + +from typing import Optional + +from pydantic import BaseModel + + +class BaseHappyDog(BaseModel): + puppy_name: str + puppy_age: Optional[int] + + +class CreateHappyDog(BaseHappyDog): + pass + + +class HappyDog(BaseHappyDog): + id: int + +``` + +Then add it to schemas `__init__.py` + +```python +# /app/schemas/__init__.py + +from .token import Token, TokenPayload, TokenRefresh +from .user import User, UserCreate, UserUpdate +from .happy_dog import HappyDog, CreateHappyDog +``` + +### 4. Create endpoint + +```python +# /app/api/endpoints/dogs.py + +from typing import Any +from fastapi import APIRouter, Depends +from sqlalchemy.ext.asyncio import AsyncSession + +from app import models, schemas +from app.api import deps + +router = APIRouter() + + +@router.post("/", response_model=schemas.HappyDog, status_code=201) +async def create_happy_dog( + dog_create: schemas.CreateHappyDog, + session: AsyncSession = Depends(deps.get_session), + current_user: models.User = Depends(deps.get_current_active_user), +) -> Any: + """ + Creates new happy dog. Only for logged users. + """ + new_dog = models.HappyDog( + puppy_name=dog_create.puppy_name, puppy_age=dog_create.puppy_age + ) + + session.add(new_dog) + await session.commit() + await session.refresh(new_dog) + + return new_dog + +``` + +Also, add it to router + +```python +# /app/api/api.py + +from fastapi import APIRouter + +from app.api.endpoints import auth, users, dogs + +api_router = APIRouter() +api_router.include_router(auth.router, prefix="/auth", tags=["auth"]) +api_router.include_router(users.router, prefix="/users", tags=["users"]) +# new content below +api_router.include_router(dogs.router, prefix="/dogs", tags=["dogs"]) + +``` + +### 5. Test it simply + +```python +# /app/tests/test_dogs.py + +import pytest +from httpx import AsyncClient +from app.models import User + +pytestmark = pytest.mark.asyncio + + +async def test_dog_endpoint(client: AsyncClient, default_user: User): + # better to create fixture auth_client or similar than repeat code with access_token + access_token = await client.post( + "/auth/access-token", + data={ + "username": "user@email.com", + "password": "password", + }, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + ) + assert access_token.status_code == 200 + access_token = access_token.json()["access_token"] + + puppy_name = "Sonia" + puppy_age = 6 + + create_dog = await client.post( + "/dogs/", + json={"puppy_name": puppy_name, "puppy_age": puppy_age}, + headers={"Authorization": f"Bearer {access_token}"}, + ) + assert create_dog.status_code == 201 + create_dog_json = create_dog.json() + assert create_dog_json["puppy_name"] == puppy_name + assert create_dog_json["puppy_age"] == puppy_age + +``` diff --git a/docs/OpenAPI_example.png b/docs/OpenAPI_example.png new file mode 100644 index 0000000..9add209 Binary files /dev/null and b/docs/OpenAPI_example.png differ diff --git a/{{cookiecutter.project_name}}/.env.example b/{{cookiecutter.project_name}}/.env.example index 25fcc67..6442612 100644 --- a/{{cookiecutter.project_name}}/.env.example +++ b/{{cookiecutter.project_name}}/.env.example @@ -1,19 +1,20 @@ -DEBUG=true -SECRET_KEY=string -ACCESS_TOKEN_EXPIRE_MINUTES=11520 +SECRET_KEY="DVnFmhwvjEhJZpuhndxjhlezxQPJmBIIkMDEmFREWQADPcUnrG" +ENVIRONMENT="DEV" +ACCESS_TOKEN_EXPIRE_MINUTES="11520" +REFRESH_TOKEN_EXPIRE_MINUTES="40320" +BACKEND_CORS_ORIGINS="http://localhost:3000,http://localhost:8001" +DEFAULT_DATABASE_HOSTNAME="localhost" +DEFAULT_DATABASE_USER="rDGJeEDqAz" +DEFAULT_DATABASE_PASSWORD="XsPQhCoEfOQZueDjsILetLDUvbvSxAMnrVtgVZpmdcSssUgbvs" +DEFAULT_DATABASE_PORT="5387" +DEFAULT_DATABASE_DB="default_db" -VERSION="0.1.0" -DESCRIPTION=string -PROJECT_NAME=string -API_STR= -BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:8001 +TEST_DATABASE_HOSTNAME="localhost" +TEST_DATABASE_USER="test" +TEST_DATABASE_PASSWORD="ywRCUjJijmQoBmWxIfLldOoITPzajPSNvTvHyugQoSqGwNcvQE" +TEST_DATABASE_PORT="37270" +TEST_DATABASE_DB="test_db" -POSTGRES_USER=postgres -POSTGRES_PASSWORD=strong_password -POSTGRES_SERVER=db -POSTGRES_DB=db -POSTGRES_PORT=4999 - -FIRST_SUPERUSER_EMAIL=example@example.com -FIRST_SUPERUSER_PASSWORD=string_password +FIRST_SUPERUSER_EMAIL="example@example.com" +FIRST_SUPERUSER_PASSWORD="OdLknKQJMUwuhpAVHvRC" \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.env.template b/{{cookiecutter.project_name}}/.env.template index d6558bb..f392a2f 100644 --- a/{{cookiecutter.project_name}}/.env.template +++ b/{{cookiecutter.project_name}}/.env.template @@ -1,19 +1,20 @@ -DEBUG=true SECRET_KEY="{{ random_ascii_string(50) }}" -ACCESS_TOKEN_EXPIRE_MINUTES=11520 +ENVIRONMENT="DEV" +ACCESS_TOKEN_EXPIRE_MINUTES="11520" +REFRESH_TOKEN_EXPIRE_MINUTES="40320" +BACKEND_CORS_ORIGINS="http://localhost:3000,http://localhost:8001" +DEFAULT_DATABASE_HOSTNAME="localhost" +DEFAULT_DATABASE_USER="{{ random_ascii_string(10) }}" +DEFAULT_DATABASE_PASSWORD="{{ random_ascii_string(50) }}" +DEFAULT_DATABASE_PORT="{{ range(4000, 7000) | random }}" +DEFAULT_DATABASE_DB="default_db" -VERSION="0.1.0" -DESCRIPTION="{{ cookiecutter.project_name }}" -PROJECT_NAME="{{ cookiecutter.project_name }}" -API_STR= -BACKEND_CORS_ORIGINS=http://localhost:3000,http://localhost:8001 +TEST_DATABASE_HOSTNAME="localhost" +TEST_DATABASE_USER="test" +TEST_DATABASE_PASSWORD="{{ random_ascii_string(50) }}" +TEST_DATABASE_PORT="{{ range(30000, 40000) | random }}" +TEST_DATABASE_DB="test_db" -POSTGRES_USER=postgres -POSTGRES_PASSWORD="{{ random_ascii_string(50) }}" -POSTGRES_SERVER=db -POSTGRES_DB=db -POSTGRES_PORT=4999 - -FIRST_SUPERUSER_EMAIL=example@example.com -FIRST_SUPERUSER_PASSWORD="{{ random_ascii_string(20) }}" +FIRST_SUPERUSER_EMAIL="example@example.com" +FIRST_SUPERUSER_PASSWORD="{{ random_ascii_string(20) }}" \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.flake8 b/{{cookiecutter.project_name}}/.flake8 index 0ffdf49..6b8856f 100644 --- a/{{cookiecutter.project_name}}/.flake8 +++ b/{{cookiecutter.project_name}}/.flake8 @@ -5,10 +5,7 @@ ignore = E203, E501, W503 exclude = __init__.py, .venv, + venv, __pycache__, .github, .vscode, - config, - locale, - webhook - app/tests/conftest.py diff --git a/{{cookiecutter.project_name}}/.gitignore b/{{cookiecutter.project_name}}/.gitignore index ab6b5f3..32562ce 100644 --- a/{{cookiecutter.project_name}}/.gitignore +++ b/{{cookiecutter.project_name}}/.gitignore @@ -1,10 +1,8 @@ -# vscode -.vscode # postgresql data -data-debug -tests_data +default_database_data +test_database_data # Byte-compiled / optimized / DLL files __pycache__/ @@ -14,7 +12,6 @@ __pycache__/ # C extensions *.so -.env # Distribution / packaging .Python diff --git a/{{cookiecutter.project_name}}/Dockerfile b/{{cookiecutter.project_name}}/Dockerfile index 37eec2c..1a5fb4a 100644 --- a/{{cookiecutter.project_name}}/Dockerfile +++ b/{{cookiecutter.project_name}}/Dockerfile @@ -1,23 +1,24 @@ +# See https://unit.nginx.org/installation/#docker-images + FROM nginx/unit:1.25.0-python3.9 -# Our Debian with Python and Nginx for python apps. -# See https://hub.docker.com/r/nginx/unit/ ENV PYTHONUNBUFFERED 1 -COPY ./app/initial.sh /docker-entrypoint.d/initial.sh -COPY ./config/config.json /docker-entrypoint.d/config.json +# Nginx unit config and init.sh will be consumed at container startup. +COPY ./app/init.sh /docker-entrypoint.d/init.sh +COPY ./nginx-unit-config.json /docker-entrypoint.d/config.json +RUN chmod +x /docker-entrypoint.d/init.sh +# Build folder for our app, only stuff that matters copied. RUN mkdir build - -# We create folder named build for our app. WORKDIR /build COPY ./app ./app +COPY ./alembic ./alembic COPY ./alembic.ini . COPY ./requirements.txt . -# We copy our app folder to the /build - +# Update, install requirements and then cleanup. RUN apt update && apt install -y python3-pip \ && pip3 install -r requirements.txt \ && apt remove -y python3-pip \ diff --git a/{{cookiecutter.project_name}}/alembic.ini b/{{cookiecutter.project_name}}/alembic.ini index dff72e9..61630d3 100644 --- a/{{cookiecutter.project_name}}/alembic.ini +++ b/{{cookiecutter.project_name}}/alembic.ini @@ -5,7 +5,7 @@ script_location = alembic # template used to generate migration files -file_template = %%(year)d%%(month).2d%%(day).2d%%(hour).2d%%(minute).2d%%(second).2d_%%(rev)s_%%(slug)s +file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d_%%(slug)s__%%(rev)s # sys.path path, will be prepended to sys.path if present. # defaults to the current working directory. diff --git a/{{cookiecutter.project_name}}/alembic/env.py b/{{cookiecutter.project_name}}/alembic/env.py index 328063c..3601bbc 100644 --- a/{{cookiecutter.project_name}}/alembic/env.py +++ b/{{cookiecutter.project_name}}/alembic/env.py @@ -31,7 +31,7 @@ def get_database_uri(): - return settings.SQLALCHEMY_DATABASE_URI + return settings.DEFAULT_SQLALCHEMY_DATABASE_URI def run_migrations_offline(): @@ -52,6 +52,7 @@ def run_migrations_offline(): target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, + compare_type=True, ) with context.begin_transaction(): @@ -59,7 +60,9 @@ def run_migrations_offline(): def do_run_migrations(connection): - context.configure(connection=connection, target_metadata=target_metadata) + context.configure( + connection=connection, target_metadata=target_metadata, compare_type=True + ) with context.begin_transaction(): context.run_migrations() diff --git a/{{cookiecutter.project_name}}/alembic/versions/20211023161504_e2318cba28d4_init.py b/{{cookiecutter.project_name}}/alembic/versions/2021_11_09_1736_init__cefce371682e.py similarity index 91% rename from {{cookiecutter.project_name}}/alembic/versions/20211023161504_e2318cba28d4_init.py rename to {{cookiecutter.project_name}}/alembic/versions/2021_11_09_1736_init__cefce371682e.py index 6336fc5..bae5d4b 100644 --- a/{{cookiecutter.project_name}}/alembic/versions/20211023161504_e2318cba28d4_init.py +++ b/{{cookiecutter.project_name}}/alembic/versions/2021_11_09_1736_init__cefce371682e.py @@ -1,8 +1,8 @@ """init -Revision ID: e2318cba28d4 +Revision ID: cefce371682e Revises: -Create Date: 2021-10-23 16:15:04.828924 +Create Date: 2021-11-09 17:36:41.970204 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = 'e2318cba28d4' +revision = 'cefce371682e' down_revision = None branch_labels = None depends_on = None diff --git a/{{cookiecutter.project_name}}/app/api/deps.py b/{{cookiecutter.project_name}}/app/api/deps.py index e5895e0..b57c214 100644 --- a/{{cookiecutter.project_name}}/app/api/deps.py +++ b/{{cookiecutter.project_name}}/app/api/deps.py @@ -13,7 +13,7 @@ from app.models import User from app.session import async_session -reusable_oauth2 = OAuth2PasswordBearer(tokenUrl=f"{settings.API_STR}/auth/access-token") +reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="auth/access-token") async def get_session() -> AsyncGenerator[AsyncSession, None]: diff --git a/{{cookiecutter.project_name}}/app/api/endpoints/auth.py b/{{cookiecutter.project_name}}/app/api/endpoints/auth.py index adab126..bb0e528 100644 --- a/{{cookiecutter.project_name}}/app/api/endpoints/auth.py +++ b/{{cookiecutter.project_name}}/app/api/endpoints/auth.py @@ -29,7 +29,7 @@ async def login_access_token( if user is None: raise HTTPException(status_code=400, detail="Incorrect email or password") - if not security.verify_password(form_data.password, user.hashed_password): + if not security.verify_password(form_data.password, user.hashed_password): # type: ignore raise HTTPException(status_code=400, detail="Incorrect email or password") access_token, expire_at = security.create_access_token(user.id) diff --git a/{{cookiecutter.project_name}}/app/api/endpoints/users.py b/{{cookiecutter.project_name}}/app/api/endpoints/users.py index fc95011..b430905 100644 --- a/{{cookiecutter.project_name}}/app/api/endpoints/users.py +++ b/{{cookiecutter.project_name}}/app/api/endpoints/users.py @@ -20,11 +20,11 @@ async def update_user_me( Update current user. """ if user_update.password is not None: - current_user.hashed_password = get_password_hash(user_update.password) + current_user.hashed_password = get_password_hash(user_update.password) # type: ignore if user_update.full_name is not None: - current_user.full_name = user_update.full_name + current_user.full_name = user_update.full_name # type: ignore if user_update.email is not None: - current_user.email = user_update.email + current_user.email = user_update.email # type: ignore session.add(current_user) await session.commit() @@ -35,7 +35,6 @@ async def update_user_me( @router.get("/me", response_model=schemas.User) async def read_user_me( - session: AsyncSession = Depends(deps.get_session), current_user: models.User = Depends(deps.get_current_active_user), ) -> Any: """ diff --git a/{{cookiecutter.project_name}}/app/conftest.py b/{{cookiecutter.project_name}}/app/conftest.py new file mode 100644 index 0000000..b6ef2c3 --- /dev/null +++ b/{{cookiecutter.project_name}}/app/conftest.py @@ -0,0 +1,10 @@ +""" +Used to execute code before running tests, in this case we want to use test database. +We don't want to mess up dev database. + +Put here any Pytest related code (it will be executed before `app/tests/...`) +""" + +import os + +os.environ["ENVIRONMENT"] = "PYTEST" diff --git a/{{cookiecutter.project_name}}/app/core/config.py b/{{cookiecutter.project_name}}/app/core/config.py index fa0d36c..226ed24 100644 --- a/{{cookiecutter.project_name}}/app/core/config.py +++ b/{{cookiecutter.project_name}}/app/core/config.py @@ -1,66 +1,94 @@ +""" +File with environment variables and general configuration logic. +`SECRET_KEY`, `ENVIRONMENT` etc. map to env variables with the same names. + +Pydantic priority ordering: + +1. (Most important, will overwrite everything) - environment variables +2. `.env` file in root folder of project +3. Default values + +For project name, version, description we use pyproject.toml +For the rest, we use file `.env` (gitignored), see `.env.example` + +`DEFAULT_SQLALCHEMY_DATABASE_URI` and `TEST_SQLALCHEMY_DATABASE_URI`: +Both are ment to be validated at the runtime, do not change unless you know +what are you doing. All the two validators do is to build full URI (TCP protocol) +to databases to avoid typo bugs. + +See https://pydantic-docs.helpmanual.io/usage/settings/ +""" + from pathlib import Path -from typing import Dict, List, Optional, Union +from typing import Dict, List, Literal, Union +import toml from pydantic import AnyHttpUrl, AnyUrl, BaseSettings, EmailStr, validator PROJECT_DIR = Path(__file__).parent.parent.parent +pyproject_content = toml.load(f"{PROJECT_DIR}/pyproject.toml")["tool"]["poetry"] class Settings(BaseSettings): - # DEBUG - DEBUG: bool - ACCESS_TOKEN_EXPIRE_MINUTES: int - - # SECURITY + # CORE SETTINGS SECRET_KEY: str - - # PROJECT NAME, API PREFIX, VERSION AND DESC, CORS ORIGINS - PROJECT_NAME: str - API_STR: str - VERSION: str - DESCRIPTION: str + ENVIRONMENT: Literal["DEV", "PYTEST", "STAGE", "PRODUCTION"] + ACCESS_TOKEN_EXPIRE_MINUTES: int + REFRESH_TOKEN_EXPIRE_MINUTES: int BACKEND_CORS_ORIGINS: Union[str, List[AnyHttpUrl]] - # POSTGRESQL - POSTGRES_USER: str - POSTGRES_PASSWORD: str - POSTGRES_SERVER: str - POSTGRES_PORT: str - POSTGRES_DB: str - SQLALCHEMY_DATABASE_URI: str = "" + # PROJECT NAME, VERSION AND DESCRIPTION + PROJECT_NAME: str = pyproject_content["name"] + VERSION: str = pyproject_content["version"] + DESCRIPTION: str = pyproject_content["description"] + + # POSTGRESQL DEFAULT DATABASE + DEFAULT_DATABASE_HOSTNAME: str + DEFAULT_DATABASE_USER: str + DEFAULT_DATABASE_PASSWORD: str + DEFAULT_DATABASE_PORT: str + DEFAULT_DATABASE_DB: str + DEFAULT_SQLALCHEMY_DATABASE_URI: str = "" + + # POSTGRESQL TEST DATABASE + TEST_DATABASE_HOSTNAME: str + TEST_DATABASE_USER: str + TEST_DATABASE_PASSWORD: str + TEST_DATABASE_PORT: str + TEST_DATABASE_DB: str + TEST_SQLALCHEMY_DATABASE_URI: str = "" # FIRST SUPERUSER FIRST_SUPERUSER_EMAIL: EmailStr FIRST_SUPERUSER_PASSWORD: str # VALIDATORS - - @validator("BACKEND_CORS_ORIGINS", pre=True) + @validator("BACKEND_CORS_ORIGINS") def _assemble_cors_origins(cls, cors_origins): if isinstance(cors_origins, str): return [item.strip() for item in cors_origins.split(",")] return cors_origins - @validator("SQLALCHEMY_DATABASE_URI", pre=True) - def _assemble_db_connection(cls, v: str, values: Dict[str, Optional[str]]) -> str: - if v != "": - return v - if values.get("DEBUG"): - postgres_server = "localhost" - else: - assert ( - values.get("POSTGRES_SERVER") is not None - ), "Variable POSTGRES_SERVER cannot be None" - - postgres_server = values.get("POSTGRES_SERVER") + @validator("DEFAULT_SQLALCHEMY_DATABASE_URI") + def _assemble_default_db_connection(cls, v: str, values: Dict[str, str]) -> str: + return AnyUrl.build( + scheme="postgresql+asyncpg", + user=values["DEFAULT_DATABASE_USER"], + password=values["DEFAULT_DATABASE_PASSWORD"], + host=values["DEFAULT_DATABASE_HOSTNAME"], + port=values["DEFAULT_DATABASE_PORT"], + path=f"/{values['DEFAULT_DATABASE_DB']}", + ) + @validator("TEST_SQLALCHEMY_DATABASE_URI") + def _assemble_test_db_connection(cls, v: str, values: Dict[str, str]) -> str: return AnyUrl.build( scheme="postgresql+asyncpg", - user=values.get("POSTGRES_USER"), - password=values.get("POSTGRES_PASSWORD"), - host=postgres_server or "localhost", - port=values.get("POSTGRES_PORT"), - path=f"/{values.get('POSTGRES_DB')}", + user=values["TEST_DATABASE_USER"], + password=values["TEST_DATABASE_PASSWORD"], + host=values["TEST_DATABASE_HOSTNAME"], + port=values["TEST_DATABASE_PORT"], + path=f"/{values['TEST_DATABASE_DB']}", ) class Config: diff --git a/{{cookiecutter.project_name}}/app/core/security.py b/{{cookiecutter.project_name}}/app/core/security.py index 3703f32..f905694 100644 --- a/{{cookiecutter.project_name}}/app/core/security.py +++ b/{{cookiecutter.project_name}}/app/core/security.py @@ -1,3 +1,9 @@ +""" +Black-box security shortcuts to generate JWT tokens and password hash/verify + +`subject` in access/refresh func may be antyhing unique to User account, `id` etc. +""" + from datetime import datetime, timedelta from typing import Any, Union @@ -27,7 +33,7 @@ def create_access_token(subject: Union[str, Any]) -> tuple[str, datetime]: def create_refresh_token(subject: Union[str, Any]) -> tuple[str, datetime]: now = datetime.utcnow() - expire = now + timedelta(days=28) + expire = now + timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES) to_encode = {"exp": expire, "sub": str(subject), "refresh": True} encoded_jwt: str = jwt.encode( diff --git a/{{cookiecutter.project_name}}/app/initial_data.py b/{{cookiecutter.project_name}}/app/initial_data.py index a3d3633..0fd073b 100644 --- a/{{cookiecutter.project_name}}/app/initial_data.py +++ b/{{cookiecutter.project_name}}/app/initial_data.py @@ -1,5 +1,11 @@ -import logging -from asyncio import get_event_loop +""" +Put here any Python code that must be runned before application startup. +It is included in `init.sh` script. + +By defualt `main` create a superuser if not exists +""" + +import asyncio from typing import Optional from sqlalchemy import select @@ -11,7 +17,7 @@ async def main() -> None: - logging.info("Start initial data") + print("Start initial data") async with async_session() as session: result = await session.execute( @@ -29,12 +35,12 @@ async def main() -> None: ) session.add(new_superuser) await session.commit() - logging.info("Superuser was created") + print("Superuser was created") else: - logging.warning("Superuser already exists in database") + print("Superuser already exists in database") - logging.info("Initial data created") + print("Initial data created") if __name__ == "__main__": - get_event_loop().run_until_complete(main()) + asyncio.run(main()) diff --git a/{{cookiecutter.project_name}}/app/main.py b/{{cookiecutter.project_name}}/app/main.py index 2803677..6d0760f 100644 --- a/{{cookiecutter.project_name}}/app/main.py +++ b/{{cookiecutter.project_name}}/app/main.py @@ -1,10 +1,20 @@ +""" +Main FastAPI app instance declaration +""" + from fastapi import FastAPI -from starlette.middleware.cors import CORSMiddleware +from fastapi.middleware.cors import CORSMiddleware from app.api.api import api_router from app.core.config import settings -app = FastAPI(title=settings.PROJECT_NAME, openapi_url="/openapi.json", docs_url="/") +app = FastAPI( + title=settings.PROJECT_NAME, + version=settings.VERSION, + description=settings.DESCRIPTION, + openapi_url="/openapi.json", + docs_url="/", +) # Set all CORS enabled origins if settings.BACKEND_CORS_ORIGINS: @@ -16,4 +26,4 @@ allow_headers=["*"], ) -app.include_router(api_router, prefix=settings.API_STR) +app.include_router(api_router) diff --git a/{{cookiecutter.project_name}}/app/models.py b/{{cookiecutter.project_name}}/app/models.py index f89b467..edb4bde 100644 --- a/{{cookiecutter.project_name}}/app/models.py +++ b/{{cookiecutter.project_name}}/app/models.py @@ -1,3 +1,9 @@ +""" +SQL Alchemy models declaration. + +Note, imported by alembic migrations logic, see `alembic/env.py` +""" + from typing import Any, cast from sqlalchemy import Column, Integer, String @@ -8,7 +14,7 @@ class User(Base): __tablename__ = "user" - id = cast(int, Column(Integer, primary_key=True, index=True)) - full_name = cast(str, Column(String(254), nullable=True)) - email = cast(str, Column(String(254), unique=True, index=True, nullable=False)) - hashed_password = cast(str, Column(String(128), nullable=False)) + id = Column(Integer, primary_key=True, index=True) + full_name = Column(String(254), nullable=True) + email = Column(String(254), unique=True, index=True, nullable=False) + hashed_password = Column(String(128), nullable=False) diff --git a/{{cookiecutter.project_name}}/app/session.py b/{{cookiecutter.project_name}}/app/session.py index a431ab0..29f3b34 100644 --- a/{{cookiecutter.project_name}}/app/session.py +++ b/{{cookiecutter.project_name}}/app/session.py @@ -3,5 +3,10 @@ from app.core.config import settings -async_engine = create_async_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) +if settings.ENVIRONMENT == "PYTEST": + SQLALCHEMY_DATABASE_URI = settings.TEST_SQLALCHEMY_DATABASE_URI +else: + SQLALCHEMY_DATABASE_URI = settings.DEFAULT_SQLALCHEMY_DATABASE_URI + +async_engine = create_async_engine(SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) async_session = sessionmaker(async_engine, expire_on_commit=False, class_=AsyncSession) diff --git a/{{cookiecutter.project_name}}/app/tests/conftest.py b/{{cookiecutter.project_name}}/app/tests/conftest.py index 3366c11..4125a1a 100644 --- a/{{cookiecutter.project_name}}/app/tests/conftest.py +++ b/{{cookiecutter.project_name}}/app/tests/conftest.py @@ -1,27 +1,16 @@ -from os import environ, getenv - -# We overwrite variables from .env to hardcoded ones to connect with tests database -# Note, order matters! -# If we write `from app.main import app` BEFORE hardcoding environment, -# It would use postgres settings defined in .env file instead of those below. - -environ["POSTGRES_USER"] = "tests" -environ["POSTGRES_PASSWORD"] = "tests" -environ["POSTGRES_DB"] = "tests" -environ["POSTGRES_HOST"] = getenv("TESTS_POSTGRES_DB_HOST") or "localhost" -environ["POSTGRES_PORT"] = "37645" - import asyncio -from typing import AsyncGenerator +from typing import AsyncGenerator, Optional import pytest from httpx import AsyncClient -from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine -from sqlalchemy.orm.session import sessionmaker +from sqlalchemy import select +from sqlalchemy.ext.asyncio import AsyncSession from app.core.config import settings +from app.core.security import get_password_hash from app.main import app -from app.models import Base +from app.models import Base, User +from app.session import async_engine, async_session @pytest.fixture(scope="session") @@ -39,18 +28,36 @@ async def client(): @pytest.fixture(scope="session") async def test_db_setup_sessionmaker(): - async_test_engine = create_async_engine(settings.SQLALCHEMY_DATABASE_URI) - async with async_test_engine.begin() as conn: - # awalys drop and create test db tables between tests session + # assert if we use TEST_DB URL for 100% + assert settings.ENVIRONMENT == "PYTEST" + assert str(async_engine.url) == settings.TEST_SQLALCHEMY_DATABASE_URI + + # always drop and create test db tables between tests session + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) await conn.run_sync(Base.metadata.create_all) - async_test_session = sessionmaker( - async_test_engine, expire_on_commit=False, class_=AsyncSession - ) - return async_test_session + return async_session @pytest.fixture async def session(test_db_setup_sessionmaker) -> AsyncGenerator[AsyncSession, None]: async with test_db_setup_sessionmaker() as session: yield session + + +@pytest.fixture +async def default_user(session: AsyncSession): + result = await session.execute(select(User).where(User.email == "user@email.com")) + user: Optional[User] = result.scalars().first() + if user is None: + new_user = User( + email="user@email.com", + hashed_password=get_password_hash("password"), + full_name="fullname", + ) + session.add(new_user) + await session.commit() + await session.refresh(new_user) + return new_user + return user diff --git a/{{cookiecutter.project_name}}/app/tests/test_auth.py b/{{cookiecutter.project_name}}/app/tests/test_auth.py index 4bac8e3..ccfaa18 100644 --- a/{{cookiecutter.project_name}}/app/tests/test_auth.py +++ b/{{cookiecutter.project_name}}/app/tests/test_auth.py @@ -1,36 +1,15 @@ -from typing import Optional - import pytest from httpx import AsyncClient -from sqlalchemy import select -from sqlalchemy.ext.asyncio import AsyncSession -from app.core.security import get_password_hash from app.models import User -# All test coroutines will be treated as marked. +# All test coroutines in file will be treated as marked (async allowed). pytestmark = pytest.mark.asyncio -@pytest.fixture -async def default_user(session: AsyncSession): - result = await session.execute(select(User).where(User.email == "user@email.com")) - user: Optional[User] = result.scalars().first() - if user is None: - new_user = User( - email="user@email.com", - hashed_password=get_password_hash("password"), - full_name="fullname", - ) - session.add(new_user) - await session.commit() - await session.refresh(new_user) - return new_user - return user - - async def test_login_endpoints(client: AsyncClient, default_user: User): + # access-token endpoint access_token = await client.post( "/auth/access-token", data={ @@ -45,6 +24,7 @@ async def test_login_endpoints(client: AsyncClient, default_user: User): access_token = token["access_token"] refresh_token = token["refresh_token"] + # test-token endpoint test_token = await client.post( "/auth/test-token", headers={"Authorization": f"Bearer {access_token}"} ) @@ -52,6 +32,7 @@ async def test_login_endpoints(client: AsyncClient, default_user: User): response_user = test_token.json() assert response_user["email"] == default_user.email + # refresh-token endpoint get_new_token = await client.post( "/auth/refresh-token", json={"refresh_token": refresh_token} ) diff --git a/{{cookiecutter.project_name}}/docker-compose.yml b/{{cookiecutter.project_name}}/docker-compose.yml index beace28..4f33f36 100644 --- a/{{cookiecutter.project_name}}/docker-compose.yml +++ b/{{cookiecutter.project_name}}/docker-compose.yml @@ -7,28 +7,30 @@ version: "3.3" # services: - db: - restart: always - image: postgres + default_database: + restart: unless-stopped + image: postgres:latest volumes: - - ./data/db:/var/lib/postgresql/data + - ./default_database_data/db:/var/lib/postgresql/data environment: - - POSTGRES_DB=${POSTGRES_DB} - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - POSTGRES_DB=${DEFAULT_DATABASE_DB} + - POSTGRES_USER=${DEFAULT_DATABASE_USER} + - POSTGRES_PASSWORD=${DEFAULT_DATABASE_PASSWORD} env_file: - .env ports: - - "${POSTGRES_PORT}:5432" + - "${DEFAULT_DATABASE_PORT}:5432" - tests_db: - restart: always - image: postgres + test_database: + restart: unless-stopped + image: postgres:latest volumes: - - ./tests_data/db:/var/lib/postgresql/data + - ./test_database_data/db:/var/lib/postgresql/data environment: - - POSTGRES_DB=tests - - POSTGRES_USER=tests - - POSTGRES_PASSWORD=tests + - POSTGRES_DB=${TEST_DATABASE_DB} + - POSTGRES_USER=${TEST_DATABASE_USER} + - POSTGRES_PASSWORD=${TEST_DATABASE_PASSWORD} + env_file: + - .env ports: - - "37645:5432" + - "${TEST_DATABASE_PORT}:5432" diff --git a/{{cookiecutter.project_name}}/init.sh b/{{cookiecutter.project_name}}/init.sh index bb2be9b..5811845 100644 --- a/{{cookiecutter.project_name}}/init.sh +++ b/{{cookiecutter.project_name}}/init.sh @@ -1,7 +1,7 @@ -#! /usr/bin/env bash +#!/bin/bash -# Run migrations +echo "Run migrations" alembic upgrade head -# Create initial data in DB +echo "Create initial data in DB" python -m app.initial_data diff --git a/{{cookiecutter.project_name}}/config/config.json b/{{cookiecutter.project_name}}/nginx-unit-config.json similarity index 100% rename from {{cookiecutter.project_name}}/config/config.json rename to {{cookiecutter.project_name}}/nginx-unit-config.json diff --git a/{{cookiecutter.project_name}}/poetry.lock b/{{cookiecutter.project_name}}/poetry.lock index 4a3dac8..ab63493 100644 --- a/{{cookiecutter.project_name}}/poetry.lock +++ b/{{cookiecutter.project_name}}/poetry.lock @@ -104,7 +104,7 @@ typecheck = ["mypy"] [[package]] name = "black" -version = "21.9b0" +version = "21.10b0" description = "The uncompromising code formatter." category = "dev" optional = false @@ -126,9 +126,9 @@ typing-extensions = [ [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"] +d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -python2 = ["typed-ast (>=1.4.2)"] +python2 = ["typed-ast (>=1.4.3)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] @@ -400,7 +400,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes [[package]] name = "importlib-resources" -version = "5.3.0" +version = "5.4.0" description = "Read resources from Python packages" category = "main" optional = false @@ -423,7 +423,7 @@ python-versions = "*" [[package]] name = "isort" -version = "5.9.3" +version = "5.10.0" description = "A Python utility / library to sort Python imports." category = "dev" optional = false @@ -494,14 +494,14 @@ python-versions = "*" [[package]] name = "packaging" -version = "21.0" +version = "21.2" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.6" [package.dependencies] -pyparsing = ">=2.0.2" +pyparsing = ">=2.0.2,<3" [[package]] name = "passlib" @@ -557,11 +557,11 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "py" -version = "1.10.0" +version = "1.11.0" description = "library with cross-python path, ini-parsing, io, code, log facilities" category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" [[package]] name = "pyasn1" @@ -581,7 +581,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" [[package]] name = "pycparser" -version = "2.20" +version = "2.21" description = "C parser in Python" category = "main" optional = false @@ -699,7 +699,7 @@ six = ">=1.4.0" [[package]] name = "regex" -version = "2021.10.8" +version = "2021.11.2" description = "Alternative regular expression module, to replace re." category = "dev" optional = false @@ -814,7 +814,7 @@ typing-extensions = ">=3.7.4" [[package]] name = "sqlalchemy2-stubs" -version = "0.0.2a18" +version = "0.0.2a19" description = "Typing Stubs for SQLAlchemy 1.4" category = "main" optional = false @@ -844,7 +844,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" [[package]] name = "tomli" -version = "1.2.1" +version = "1.2.2" description = "A lil' TOML parser" category = "dev" optional = false @@ -962,8 +962,8 @@ bcrypt = [ {file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"}, ] black = [ - {file = "black-21.9b0-py3-none-any.whl", hash = "sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115"}, - {file = "black-21.9b0.tar.gz", hash = "sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91"}, + {file = "black-21.10b0-py3-none-any.whl", hash = "sha256:6eb7448da9143ee65b856a5f3676b7dda98ad9abe0f87fce8c59291f15e82a5b"}, + {file = "black-21.10b0.tar.gz", hash = "sha256:a9952229092e325fe5f3dae56d81f639b23f7131eb840781947e4b2886030f33"}, ] certifi = [ {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, @@ -1238,16 +1238,16 @@ importlib-metadata = [ {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"}, ] importlib-resources = [ - {file = "importlib_resources-5.3.0-py3-none-any.whl", hash = "sha256:7a65eb0d8ee98eedab76e6deb51195c67f8e575959f6356a6e15fd7e1148f2a3"}, - {file = "importlib_resources-5.3.0.tar.gz", hash = "sha256:f2e58e721b505a79abe67f5868d99f8886aec8594c962c7490d0c22925f518da"}, + {file = "importlib_resources-5.4.0-py3-none-any.whl", hash = "sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45"}, + {file = "importlib_resources-5.4.0.tar.gz", hash = "sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b"}, ] iniconfig = [ {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, ] isort = [ - {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, - {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, + {file = "isort-5.10.0-py3-none-any.whl", hash = "sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f"}, + {file = "isort-5.10.0.tar.gz", hash = "sha256:e52ff6d38012b131628cf0f26c51e7bd3a7c81592eefe3ac71411e692f1b9345"}, ] mako = [ {file = "Mako-1.1.5-py2.py3-none-any.whl", hash = "sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23"}, @@ -1343,8 +1343,8 @@ mypy-extensions = [ {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, ] packaging = [ - {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, - {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, + {file = "packaging-21.2-py3-none-any.whl", hash = "sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0"}, + {file = "packaging-21.2.tar.gz", hash = "sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966"}, ] passlib = [ {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, @@ -1363,8 +1363,8 @@ pluggy = [ {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, ] py = [ - {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, - {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, ] pyasn1 = [ {file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"}, @@ -1386,8 +1386,8 @@ pycodestyle = [ {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, ] pycparser = [ - {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, - {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, ] pydantic = [ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"}, @@ -1441,53 +1441,55 @@ python-multipart = [ {file = "python-multipart-0.0.5.tar.gz", hash = "sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43"}, ] regex = [ - {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:094a905e87a4171508c2a0e10217795f83c636ccc05ddf86e7272c26e14056ae"}, - {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"}, - {file = "regex-2021.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b0f2f874c6a157c91708ac352470cb3bef8e8814f5325e3c5c7a0533064c6a24"}, - {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"}, - {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e"}, - {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01"}, - {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc"}, - {file = "regex-2021.10.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"}, - {file = "regex-2021.10.8-cp310-cp310-win32.whl", hash = "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7"}, - {file = "regex-2021.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152"}, - {file = "regex-2021.10.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820"}, - {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3"}, - {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9"}, - {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838"}, - {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287"}, - {file = "regex-2021.10.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92"}, - {file = "regex-2021.10.8-cp36-cp36m-win32.whl", hash = "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f"}, - {file = "regex-2021.10.8-cp36-cp36m-win_amd64.whl", hash = "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee"}, - {file = "regex-2021.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4"}, - {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf"}, - {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f"}, - {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61"}, - {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e"}, - {file = "regex-2021.10.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd"}, - {file = "regex-2021.10.8-cp37-cp37m-win32.whl", hash = "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432"}, - {file = "regex-2021.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca"}, - {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19b8f6d23b2dc93e8e1e7e288d3010e58fafed323474cf7f27ab9451635136d9"}, - {file = "regex-2021.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59"}, - {file = "regex-2021.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:951be934dc25d8779d92b530e922de44dda3c82a509cdb5d619f3a0b1491fafa"}, - {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741"}, - {file = "regex-2021.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354"}, - {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f"}, - {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d"}, - {file = "regex-2021.10.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3"}, - {file = "regex-2021.10.8-cp38-cp38-win32.whl", hash = "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593"}, - {file = "regex-2021.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1"}, - {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6dcf53d35850ce938b4f044a43b33015ebde292840cef3af2c8eb4c860730fff"}, - {file = "regex-2021.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991"}, - {file = "regex-2021.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e2ec1c106d3f754444abf63b31e5c4f9b5d272272a491fa4320475aba9e8157c"}, - {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3"}, - {file = "regex-2021.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb"}, - {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493"}, - {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2"}, - {file = "regex-2021.10.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b"}, - {file = "regex-2021.10.8-cp39-cp39-win32.whl", hash = "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b"}, - {file = "regex-2021.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700"}, - {file = "regex-2021.10.8.tar.gz", hash = "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a"}, + {file = "regex-2021.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:897c539f0f3b2c3a715be651322bef2167de1cdc276b3f370ae81a3bda62df71"}, + {file = "regex-2021.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:886f459db10c0f9d17c87d6594e77be915f18d343ee138e68d259eb385f044a8"}, + {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:075b0fdbaea81afcac5a39a0d1bb91de887dd0d93bf692a5dd69c430e7fc58cb"}, + {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6238d30dcff141de076344cf7f52468de61729c2f70d776fce12f55fe8df790"}, + {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fab29411d75c2eb48070020a40f80255936d7c31357b086e5931c107d48306e"}, + {file = "regex-2021.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0148988af0182a0a4e5020e7c168014f2c55a16d11179610f7883dd48ac0ebe"}, + {file = "regex-2021.11.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be30cd315db0168063a1755fa20a31119da91afa51da2907553493516e165640"}, + {file = "regex-2021.11.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e9cec3a62d146e8e122d159ab93ac32c988e2ec0dcb1e18e9e53ff2da4fbd30c"}, + {file = "regex-2021.11.2-cp310-cp310-win32.whl", hash = "sha256:41c66bd6750237a8ed23028a6c9173dc0c92dc24c473e771d3bfb9ee817700c3"}, + {file = "regex-2021.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:0075fe4e2c2720a685fef0f863edd67740ff78c342cf20b2a79bc19388edf5db"}, + {file = "regex-2021.11.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0ed3465acf8c7c10aa2e0f3d9671da410ead63b38a77283ef464cbb64275df58"}, + {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab1fea8832976ad0bebb11f652b692c328043057d35e9ebc78ab0a7a30cf9a70"}, + {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cb1e44d860345ab5d4f533b6c37565a22f403277f44c4d2d5e06c325da959883"}, + {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9486ebda015913909bc28763c6b92fcc3b5e5a67dee4674bceed112109f5dfb8"}, + {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20605bfad484e1341b2cbfea0708e4b211d233716604846baa54b94821f487cb"}, + {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f20f9f430c33597887ba9bd76635476928e76cad2981643ca8be277b8e97aa96"}, + {file = "regex-2021.11.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1d85ca137756d62c8138c971453cafe64741adad1f6a7e63a22a5a8abdbd19fa"}, + {file = "regex-2021.11.2-cp36-cp36m-win32.whl", hash = "sha256:af23b9ca9a874ef0ec20e44467b8edd556c37b0f46f93abfa93752ea7c0e8d1e"}, + {file = "regex-2021.11.2-cp36-cp36m-win_amd64.whl", hash = "sha256:070336382ca92c16c45b4066c4ba9fa83fb0bd13d5553a82e07d344df8d58a84"}, + {file = "regex-2021.11.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ef4e53e2fdc997d91f5b682f81f7dc9661db9a437acce28745d765d251902d85"}, + {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35ed5714467fc606551db26f80ee5d6aa1f01185586a7bccd96f179c4b974a11"}, + {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ee36d5113b6506b97f45f2e8447cb9af146e60e3f527d93013d19f6d0405f3b"}, + {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4fba661a4966adbd2c3c08d3caad6822ecb6878f5456588e2475ae23a6e47929"}, + {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77f9d16f7970791f17ecce7e7f101548314ed1ee2583d4268601f30af3170856"}, + {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f6a28e87ba69f3a4f30d775b179aac55be1ce59f55799328a0d9b6df8f16b39d"}, + {file = "regex-2021.11.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9267e4fba27e6dd1008c4f2983cc548c98b4be4444e3e342db11296c0f45512f"}, + {file = "regex-2021.11.2-cp37-cp37m-win32.whl", hash = "sha256:d4bfe3bc3976ccaeb4ae32f51e631964e2f0e85b2b752721b7a02de5ce3b7f27"}, + {file = "regex-2021.11.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2bb7cae741de1aa03e3dd3a7d98c304871eb155921ca1f0d7cc11f5aade913fd"}, + {file = "regex-2021.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:23f93e74409c210de4de270d4bf88fb8ab736a7400f74210df63a93728cf70d6"}, + {file = "regex-2021.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d8ee91e1c295beb5c132ebd78616814de26fedba6aa8687ea460c7f5eb289b72"}, + {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e3ff69ab203b54ce5c480c3ccbe959394ea5beef6bd5ad1785457df7acea92e"}, + {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3c00cb5c71da655e1e5161481455479b613d500dd1bd252aa01df4f037c641f"}, + {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4abf35e16f4b639daaf05a2602c1b1d47370e01babf9821306aa138924e3fe92"}, + {file = "regex-2021.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb11c982a849dc22782210b01d0c1b98eb3696ce655d58a54180774e4880ac66"}, + {file = "regex-2021.11.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e3755e0f070bc31567dfe447a02011bfa8444239b3e9e5cca6773a22133839"}, + {file = "regex-2021.11.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0621c90f28d17260b41838b22c81a79ff436141b322960eb49c7b3f91d1cbab6"}, + {file = "regex-2021.11.2-cp38-cp38-win32.whl", hash = "sha256:8fbe1768feafd3d0156556677b8ff234c7bf94a8110e906b2d73506f577a3269"}, + {file = "regex-2021.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:f9ee98d658a146cb6507be720a0ce1b44f2abef8fb43c2859791d91aace17cd5"}, + {file = "regex-2021.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3794cea825f101fe0df9af8a00f9fad8e119c91e39a28636b95ee2b45b6c2e5"}, + {file = "regex-2021.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3576e173e7b4f88f683b4de7db0c2af1b209bb48b2bf1c827a6f3564fad59a97"}, + {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b4f4810117a9072a5aa70f7fea5f86fa9efbe9a798312e0a05044bd707cc33"}, + {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f5930d334c2f607711d54761956aedf8137f83f1b764b9640be21d25a976f3a4"}, + {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:956187ff49db7014ceb31e88fcacf4cf63371e6e44d209cf8816cd4a2d61e11a"}, + {file = "regex-2021.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17e095f7f96a4b9f24b93c2c915f31a5201a6316618d919b0593afb070a5270e"}, + {file = "regex-2021.11.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a56735c35a3704603d9d7b243ee06139f0837bcac2171d9ba1d638ce1df0742a"}, + {file = "regex-2021.11.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:adf35d88d9cffc202e6046e4c32e1e11a1d0238b2fcf095c94f109e510ececea"}, + {file = "regex-2021.11.2-cp39-cp39-win32.whl", hash = "sha256:30fe317332de0e50195665bc61a27d46e903d682f94042c36b3f88cb84bd7958"}, + {file = "regex-2021.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:85289c25f658e3260b00178757c87f033f3d4b3e40aa4abdd4dc875ff11a94fb"}, + {file = "regex-2021.11.2.tar.gz", hash = "sha256:5e85dcfc5d0f374955015ae12c08365b565c6f1eaf36dd182476a4d8e5a1cdb7"}, ] requests = [ {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, @@ -1552,8 +1554,8 @@ sqlalchemy-stubs = [ {file = "sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5"}, ] sqlalchemy2-stubs = [ - {file = "sqlalchemy2-stubs-0.0.2a18.tar.gz", hash = "sha256:513f8f504e7a869e6a584b9cbfa65b7d817d017ff01af61855f62087735561a9"}, - {file = "sqlalchemy2_stubs-0.0.2a18-py3-none-any.whl", hash = "sha256:75ec8ce53db5a85884adcb9f249751bc3aefb4c24fd2b5bd62860113eea4b37a"}, + {file = "sqlalchemy2-stubs-0.0.2a19.tar.gz", hash = "sha256:2117c48ce5acfe33bf9c9bfce2a981632d931949e68fa313aa5c2a3bc980ca7a"}, + {file = "sqlalchemy2_stubs-0.0.2a19-py3-none-any.whl", hash = "sha256:aac7dca77a2c49e5f0934976421d5e25ae4dc5e27db48c01e055f81caa1e3ead"}, ] starlette = [ {file = "starlette-0.14.2-py3-none-any.whl", hash = "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed"}, @@ -1564,8 +1566,8 @@ toml = [ {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, ] tomli = [ - {file = "tomli-1.2.1-py3-none-any.whl", hash = "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f"}, - {file = "tomli-1.2.1.tar.gz", hash = "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"}, + {file = "tomli-1.2.2-py3-none-any.whl", hash = "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade"}, + {file = "tomli-1.2.2.tar.gz", hash = "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee"}, ] typed-ast = [ {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index f0046a9..e4afaae 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "{{cookiecutter.project_name}}" -version = "0.1.0" -description = "" +version = "0.1.0-alpha" +description = "FastAPI project generated using minimal-fastapi-postgres-template." authors = ["admin "] [tool.poetry.dependencies] diff --git a/{{cookiecutter.project_name}}/requirements-dev.txt b/{{cookiecutter.project_name}}/requirements-dev.txt index 5edd0c7..0bbda67 100644 --- a/{{cookiecutter.project_name}}/requirements-dev.txt +++ b/{{cookiecutter.project_name}}/requirements-dev.txt @@ -37,9 +37,9 @@ bcrypt==3.2.0; python_version >= "3.6" \ --hash=sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55 \ --hash=sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34 \ --hash=sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29 -black==21.9b0; python_full_version >= "3.6.2" and python_full_version < "4.0.0" \ - --hash=sha256:380f1b5da05e5a1429225676655dddb96f5ae8c75bdf91e53d798871b902a115 \ - --hash=sha256:7de4cfc7eb6b710de325712d40125689101d21d25283eed7e9998722cf10eb91 +black==21.10b0; python_full_version >= "3.6.2" and python_full_version < "4.0.0" \ + --hash=sha256:6eb7448da9143ee65b856a5f3676b7dda98ad9abe0f87fce8c59291f15e82a5b \ + --hash=sha256:a9952229092e325fe5f3dae56d81f639b23f7131eb840781947e4b2886030f33 certifi==2021.10.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" \ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569 \ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 @@ -291,15 +291,15 @@ immutables==0.16; python_version < "3.7" and python_version >= "3.6" \ importlib-metadata==4.8.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.6" \ --hash=sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15 \ --hash=sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1 -importlib-resources==5.3.0; python_version < "3.9" and python_version >= "3.6" \ - --hash=sha256:7a65eb0d8ee98eedab76e6deb51195c67f8e575959f6356a6e15fd7e1148f2a3 \ - --hash=sha256:f2e58e721b505a79abe67f5868d99f8886aec8594c962c7490d0c22925f518da +importlib-resources==5.4.0; python_version < "3.9" and python_version >= "3.6" \ + --hash=sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45 \ + --hash=sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b iniconfig==1.1.1; python_version >= "3.6" \ --hash=sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3 \ --hash=sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32 -isort==5.9.3; python_full_version >= "3.6.1" and python_version < "4.0" \ - --hash=sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2 \ - --hash=sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899 +isort==5.10.0; python_full_version >= "3.6.1" and python_version < "4.0" \ + --hash=sha256:1a18ccace2ed8910bd9458b74a3ecbafd7b2f581301b0ab65cfdd4338272d76f \ + --hash=sha256:e52ff6d38012b131628cf0f26c51e7bd3a7c81592eefe3ac71411e692f1b9345 mako==1.1.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \ --hash=sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23 \ --hash=sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3 @@ -388,9 +388,9 @@ mypy==0.910; python_version >= "3.5" \ --hash=sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8 \ --hash=sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d \ --hash=sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150 -packaging==21.0; python_version >= "3.6" \ - --hash=sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14 \ - --hash=sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7 +packaging==21.2; python_version >= "3.6" \ + --hash=sha256:14317396d1e8cdb122989b916fa2c7e9ca8e2be9e8060a6eff75b6b7b4d8a7e0 \ + --hash=sha256:096d689d78ca690e4cd8a89568ba06d07ca097e3306a4381635073ca91479966 passlib==1.7.4 \ --hash=sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1 \ --hash=sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04 @@ -403,9 +403,9 @@ platformdirs==2.4.0; python_full_version >= "3.6.2" and python_full_version < "4 pluggy==1.0.0; python_version >= "3.6" \ --hash=sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3 \ --hash=sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159 -py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \ - --hash=sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a \ - --hash=sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3 +py==1.11.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" \ + --hash=sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378 \ + --hash=sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719 pyasn1==0.4.8; python_version >= "3.5" and python_version < "4" \ --hash=sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3 \ --hash=sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf \ @@ -423,9 +423,9 @@ pyasn1==0.4.8; python_version >= "3.5" and python_version < "4" \ pycodestyle==2.7.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" \ --hash=sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068 \ --hash=sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef -pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" \ - --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 \ - --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 +pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 pydantic==1.8.2; python_full_version >= "3.6.1" \ --hash=sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739 \ --hash=sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4 \ @@ -469,54 +469,56 @@ python-jose==3.3.0 \ --hash=sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a python-multipart==0.0.5 \ --hash=sha256:f7bb5f611fc600d15fa47b3974c8aa16e93724513b49b5f95c81e6624c83fa43 -regex==2021.10.8; python_full_version >= "3.6.2" and python_full_version < "4.0.0" \ - --hash=sha256:094a905e87a4171508c2a0e10217795f83c636ccc05ddf86e7272c26e14056ae \ - --hash=sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072 \ - --hash=sha256:b0f2f874c6a157c91708ac352470cb3bef8e8814f5325e3c5c7a0533064c6a24 \ - --hash=sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361 \ - --hash=sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e \ - --hash=sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01 \ - --hash=sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc \ - --hash=sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f \ - --hash=sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7 \ - --hash=sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152 \ - --hash=sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820 \ - --hash=sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3 \ - --hash=sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9 \ - --hash=sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838 \ - --hash=sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287 \ - --hash=sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92 \ - --hash=sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f \ - --hash=sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee \ - --hash=sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4 \ - --hash=sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf \ - --hash=sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f \ - --hash=sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61 \ - --hash=sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e \ - --hash=sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd \ - --hash=sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432 \ - --hash=sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca \ - --hash=sha256:19b8f6d23b2dc93e8e1e7e288d3010e58fafed323474cf7f27ab9451635136d9 \ - --hash=sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59 \ - --hash=sha256:951be934dc25d8779d92b530e922de44dda3c82a509cdb5d619f3a0b1491fafa \ - --hash=sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741 \ - --hash=sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354 \ - --hash=sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f \ - --hash=sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d \ - --hash=sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3 \ - --hash=sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593 \ - --hash=sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1 \ - --hash=sha256:6dcf53d35850ce938b4f044a43b33015ebde292840cef3af2c8eb4c860730fff \ - --hash=sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991 \ - --hash=sha256:e2ec1c106d3f754444abf63b31e5c4f9b5d272272a491fa4320475aba9e8157c \ - --hash=sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3 \ - --hash=sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb \ - --hash=sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493 \ - --hash=sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2 \ - --hash=sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b \ - --hash=sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b \ - --hash=sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700 \ - --hash=sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a +regex==2021.11.2; python_full_version >= "3.6.2" and python_full_version < "4.0.0" \ + --hash=sha256:897c539f0f3b2c3a715be651322bef2167de1cdc276b3f370ae81a3bda62df71 \ + --hash=sha256:886f459db10c0f9d17c87d6594e77be915f18d343ee138e68d259eb385f044a8 \ + --hash=sha256:075b0fdbaea81afcac5a39a0d1bb91de887dd0d93bf692a5dd69c430e7fc58cb \ + --hash=sha256:c6238d30dcff141de076344cf7f52468de61729c2f70d776fce12f55fe8df790 \ + --hash=sha256:7fab29411d75c2eb48070020a40f80255936d7c31357b086e5931c107d48306e \ + --hash=sha256:f0148988af0182a0a4e5020e7c168014f2c55a16d11179610f7883dd48ac0ebe \ + --hash=sha256:be30cd315db0168063a1755fa20a31119da91afa51da2907553493516e165640 \ + --hash=sha256:e9cec3a62d146e8e122d159ab93ac32c988e2ec0dcb1e18e9e53ff2da4fbd30c \ + --hash=sha256:41c66bd6750237a8ed23028a6c9173dc0c92dc24c473e771d3bfb9ee817700c3 \ + --hash=sha256:0075fe4e2c2720a685fef0f863edd67740ff78c342cf20b2a79bc19388edf5db \ + --hash=sha256:0ed3465acf8c7c10aa2e0f3d9671da410ead63b38a77283ef464cbb64275df58 \ + --hash=sha256:ab1fea8832976ad0bebb11f652b692c328043057d35e9ebc78ab0a7a30cf9a70 \ + --hash=sha256:cb1e44d860345ab5d4f533b6c37565a22f403277f44c4d2d5e06c325da959883 \ + --hash=sha256:9486ebda015913909bc28763c6b92fcc3b5e5a67dee4674bceed112109f5dfb8 \ + --hash=sha256:20605bfad484e1341b2cbfea0708e4b211d233716604846baa54b94821f487cb \ + --hash=sha256:f20f9f430c33597887ba9bd76635476928e76cad2981643ca8be277b8e97aa96 \ + --hash=sha256:1d85ca137756d62c8138c971453cafe64741adad1f6a7e63a22a5a8abdbd19fa \ + --hash=sha256:af23b9ca9a874ef0ec20e44467b8edd556c37b0f46f93abfa93752ea7c0e8d1e \ + --hash=sha256:070336382ca92c16c45b4066c4ba9fa83fb0bd13d5553a82e07d344df8d58a84 \ + --hash=sha256:ef4e53e2fdc997d91f5b682f81f7dc9661db9a437acce28745d765d251902d85 \ + --hash=sha256:35ed5714467fc606551db26f80ee5d6aa1f01185586a7bccd96f179c4b974a11 \ + --hash=sha256:7ee36d5113b6506b97f45f2e8447cb9af146e60e3f527d93013d19f6d0405f3b \ + --hash=sha256:4fba661a4966adbd2c3c08d3caad6822ecb6878f5456588e2475ae23a6e47929 \ + --hash=sha256:77f9d16f7970791f17ecce7e7f101548314ed1ee2583d4268601f30af3170856 \ + --hash=sha256:f6a28e87ba69f3a4f30d775b179aac55be1ce59f55799328a0d9b6df8f16b39d \ + --hash=sha256:9267e4fba27e6dd1008c4f2983cc548c98b4be4444e3e342db11296c0f45512f \ + --hash=sha256:d4bfe3bc3976ccaeb4ae32f51e631964e2f0e85b2b752721b7a02de5ce3b7f27 \ + --hash=sha256:2bb7cae741de1aa03e3dd3a7d98c304871eb155921ca1f0d7cc11f5aade913fd \ + --hash=sha256:23f93e74409c210de4de270d4bf88fb8ab736a7400f74210df63a93728cf70d6 \ + --hash=sha256:d8ee91e1c295beb5c132ebd78616814de26fedba6aa8687ea460c7f5eb289b72 \ + --hash=sha256:2e3ff69ab203b54ce5c480c3ccbe959394ea5beef6bd5ad1785457df7acea92e \ + --hash=sha256:e3c00cb5c71da655e1e5161481455479b613d500dd1bd252aa01df4f037c641f \ + --hash=sha256:4abf35e16f4b639daaf05a2602c1b1d47370e01babf9821306aa138924e3fe92 \ + --hash=sha256:bb11c982a849dc22782210b01d0c1b98eb3696ce655d58a54180774e4880ac66 \ + --hash=sha256:07e3755e0f070bc31567dfe447a02011bfa8444239b3e9e5cca6773a22133839 \ + --hash=sha256:0621c90f28d17260b41838b22c81a79ff436141b322960eb49c7b3f91d1cbab6 \ + --hash=sha256:8fbe1768feafd3d0156556677b8ff234c7bf94a8110e906b2d73506f577a3269 \ + --hash=sha256:f9ee98d658a146cb6507be720a0ce1b44f2abef8fb43c2859791d91aace17cd5 \ + --hash=sha256:b3794cea825f101fe0df9af8a00f9fad8e119c91e39a28636b95ee2b45b6c2e5 \ + --hash=sha256:3576e173e7b4f88f683b4de7db0c2af1b209bb48b2bf1c827a6f3564fad59a97 \ + --hash=sha256:48b4f4810117a9072a5aa70f7fea5f86fa9efbe9a798312e0a05044bd707cc33 \ + --hash=sha256:f5930d334c2f607711d54761956aedf8137f83f1b764b9640be21d25a976f3a4 \ + --hash=sha256:956187ff49db7014ceb31e88fcacf4cf63371e6e44d209cf8816cd4a2d61e11a \ + --hash=sha256:17e095f7f96a4b9f24b93c2c915f31a5201a6316618d919b0593afb070a5270e \ + --hash=sha256:a56735c35a3704603d9d7b243ee06139f0837bcac2171d9ba1d638ce1df0742a \ + --hash=sha256:adf35d88d9cffc202e6046e4c32e1e11a1d0238b2fcf095c94f109e510ececea \ + --hash=sha256:30fe317332de0e50195665bc61a27d46e903d682f94042c36b3f88cb84bd7958 \ + --hash=sha256:85289c25f658e3260b00178757c87f033f3d4b3e40aa4abdd4dc875ff11a94fb \ + --hash=sha256:5e85dcfc5d0f374955015ae12c08365b565c6f1eaf36dd182476a4d8e5a1cdb7 requests==2.26.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \ --hash=sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24 \ --hash=sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7 @@ -535,9 +537,9 @@ sniffio==1.2.0; python_version >= "3.6" \ sqlalchemy-stubs==0.4 \ --hash=sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae \ --hash=sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5 -sqlalchemy2-stubs==0.0.2a18; python_version >= "3.6" \ - --hash=sha256:513f8f504e7a869e6a584b9cbfa65b7d817d017ff01af61855f62087735561a9 \ - --hash=sha256:75ec8ce53db5a85884adcb9f249751bc3aefb4c24fd2b5bd62860113eea4b37a +sqlalchemy2-stubs==0.0.2a19; python_version >= "3.6" \ + --hash=sha256:2117c48ce5acfe33bf9c9bfce2a981632d931949e68fa313aa5c2a3bc980ca7a \ + --hash=sha256:aac7dca77a2c49e5f0934976421d5e25ae4dc5e27db48c01e055f81caa1e3ead sqlalchemy==1.4.26; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \ --hash=sha256:c2f2114b0968a280f94deeeaa31cfbac9175e6ac7bd3058b3ce6e054ecd762b3 \ --hash=sha256:91efbda4e6d311812f23996242bad7665c1392209554f8a31ec6db757456db5c \ @@ -581,9 +583,9 @@ starlette==0.14.2; python_version >= "3.6" and python_full_version >= "3.6.1" \ toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" \ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f -tomli==1.2.1; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.6" \ - --hash=sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f \ - --hash=sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442 +tomli==1.2.2; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version >= "3.6" \ + --hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade \ + --hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee typed-ast==1.4.3; python_full_version >= "3.6.2" and python_full_version < "4.0.0" and python_version < "3.8" and python_version >= "3.5" \ --hash=sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6 \ --hash=sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075 \ diff --git a/{{cookiecutter.project_name}}/requirements.txt b/{{cookiecutter.project_name}}/requirements.txt index 18cf782..da97e25 100644 --- a/{{cookiecutter.project_name}}/requirements.txt +++ b/{{cookiecutter.project_name}}/requirements.txt @@ -185,9 +185,9 @@ idna==3.3; python_full_version >= "3.6.1" and python_version >= "3.5" \ importlib-metadata==4.8.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.6.0" and python_version < "3.8" and python_version >= "3.6" \ --hash=sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15 \ --hash=sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1 -importlib-resources==5.3.0; python_version < "3.9" and python_version >= "3.6" \ - --hash=sha256:7a65eb0d8ee98eedab76e6deb51195c67f8e575959f6356a6e15fd7e1148f2a3 \ - --hash=sha256:f2e58e721b505a79abe67f5868d99f8886aec8594c962c7490d0c22925f518da +importlib-resources==5.4.0; python_version < "3.9" and python_version >= "3.6" \ + --hash=sha256:33a95faed5fc19b4bc16b29a6eeae248a3fe69dd55d4d229d2b480e23eeaad45 \ + --hash=sha256:d756e2f85dd4de2ba89be0b21dba2a3bbec2e871a42a3a16719258a11f87506b mako==1.1.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" \ --hash=sha256:6804ee66a7f6a6416910463b00d76a7b25194cd27f1918500c5bd7be2a088a23 \ --hash=sha256:169fa52af22a91900d852e937400e79f535496191c63712e3b9fda5a9bed6fc3 @@ -263,9 +263,9 @@ pyasn1==0.4.8; python_version >= "3.5" and python_version < "4" \ --hash=sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359 \ --hash=sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776 \ --hash=sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba -pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" \ - --hash=sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705 \ - --hash=sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0 +pycparser==2.21; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0" \ + --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \ + --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206 pydantic==1.8.2; python_full_version >= "3.6.1" \ --hash=sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739 \ --hash=sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4 \ @@ -306,9 +306,9 @@ rsa==4.7.2; python_version >= "3.5" and python_version < "4" \ six==1.16.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.3.0" \ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 \ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 -sqlalchemy2-stubs==0.0.2a18; python_version >= "3.6" \ - --hash=sha256:513f8f504e7a869e6a584b9cbfa65b7d817d017ff01af61855f62087735561a9 \ - --hash=sha256:75ec8ce53db5a85884adcb9f249751bc3aefb4c24fd2b5bd62860113eea4b37a +sqlalchemy2-stubs==0.0.2a19; python_version >= "3.6" \ + --hash=sha256:2117c48ce5acfe33bf9c9bfce2a981632d931949e68fa313aa5c2a3bc980ca7a \ + --hash=sha256:aac7dca77a2c49e5f0934976421d5e25ae4dc5e27db48c01e055f81caa1e3ead sqlalchemy==1.4.26; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0") \ --hash=sha256:c2f2114b0968a280f94deeeaa31cfbac9175e6ac7bd3058b3ce6e054ecd762b3 \ --hash=sha256:91efbda4e6d311812f23996242bad7665c1392209554f8a31ec6db757456db5c \