1
0
mirror of https://github.com/ijaric/voice_assistant.git synced 2025-05-24 14:33:26 +00:00

Merge pull request #32 from ijaric/fix/#6_fix_template

Исправление мелких багов в "Шаблоне" #6
This commit is contained in:
Artem Litvinov 2023-10-03 15:38:25 +01:00 committed by GitHub
commit a6700b4e17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 219 additions and 2621 deletions

View File

@ -1,14 +1,15 @@
POSTGRES_PROTOCOL=postgresql+asyncpg
POSTGRES_DRIVER=postgresql+asyncpg
POSTGRES_HOST=db
POSTGRES_PORT=5432
POSTGRES_USER=user
POSTGRES_PASSWORD=Qwe123
POSTGRES_NAME=api_db
POSTGRES_DB_NAME=api_db
NGINX_PORT=80
API_HOST=0.0.0.0
API_PORT=8000
JWT_SECRET_KEY=v9LctjUWwol4XbvczPiLFMDtZ8aal7mm
JWT_ALGORITHM=HS256
APP_RELOAD=True

View File

@ -17,7 +17,7 @@ config = context.config
if config.config_file_name is not None:
fileConfig(config.config_file_name)
config.set_main_option("sqlalchemy.url", app_settings.settings.postgres.dsn)
config.set_main_option("sqlalchemy.url", app_settings.Settings().postgres.dsn)
target_metadata = models.Base.metadata

View File

@ -1,5 +1,5 @@
from .health import *
__all__ = [
"health_router",
"basic_router",
]

View File

@ -1,5 +1,5 @@
from .liveness_probe import router as health_router
from .liveness_probe import basic_router
__all__ = [
"health_router",
"basic_router",
]

View File

@ -2,10 +2,10 @@ import fastapi
import lib.api.v1.schemas as api_shemas
router = fastapi.APIRouter()
basic_router = fastapi.APIRouter()
@router.get(
@basic_router.get(
"/",
response_model=api_shemas.HealthResponseModel,
summary="Статус работоспособности",

View File

@ -1,25 +0,0 @@
import uuid
import sqlalchemy
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declared_attr
class BaseMixin:
@declared_attr
def id(cls):
return sqlalchemy.Column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
@declared_attr
def created_at(cls):
return sqlalchemy.Column(sqlalchemy.DateTime, server_default=sqlalchemy.sql.func.now())
@declared_attr
def updated_at(cls):
return sqlalchemy.Column(sqlalchemy.DateTime, server_default=sqlalchemy.sql.func.now())

View File

@ -1,6 +1,5 @@
from .base import *
from .base import HealthResponseModel
__all__ = [
"HealthResponseModel",
"TokenResponseModel",
]

View File

@ -1,12 +1,5 @@
import uuid
import pydantic
class TokenResponseModel(pydantic.BaseModel):
sub: uuid.UUID
exp: int | None = None
class HealthResponseModel(pydantic.BaseModel):
status: str = pydantic.Field(default=..., examples=["healthy"], description="Схема доступности сервиса")

View File

@ -1,26 +0,0 @@
import fastapi
from jose import JWTError, jwt
from pydantic import ValidationError
import lib.app.settings as app_settings
from lib.api.v1 import schemas as app_schemas
app = fastapi.FastAPI()
settings = app_settings.settings
security = fastapi.security.HTTPBearer()
def get_token_data(
authorization: fastapi.security.HTTPAuthorizationCredentials = fastapi.Security(security),
) -> app_schemas.entity.Token:
token = authorization.credentials
try:
secret_key = settings.project.jwt_secret_key
payload = jwt.decode(token, secret_key, algorithms=["HS256"])
return app_schemas.entity.Token(**payload)
except (JWTError, ValidationError):
raise fastapi.HTTPException(
status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
)

View File

@ -4,8 +4,8 @@ from .settings import Settings
__all__ = [
"Application",
"Settings",
"ApplicationError",
"DisposeError",
"Settings",
"StartServerError",
]

View File

@ -10,6 +10,7 @@ import lib.api.v1.handlers as api_v1_handlers
import lib.app.errors as app_errors
import lib.app.settings as app_settings
import lib.app.split_settings as app_split_settings
import lib.clients as clients
logger = logging.getLogger(__name__)
@ -43,6 +44,15 @@ class Application:
# Global clients
logger.info("Initializing global clients")
postgres_client = clients.AsyncPostgresClient(settings=settings)
http_client = clients.get_async_http_session()
disposable_resources.append(
DisposableResource(
name="postgres_client",
dispose_callback=postgres_client.dispose_callback(),
)
)
# Clients
@ -63,10 +73,10 @@ class Application:
# Handlers
logger.info("Initializing handlers")
# liveness_probe_handler = health_handlers.LivenessProbeHandler()
liveness_probe_handler = api_v1_handlers.basic_router
logger.info("Creating application")
# aio_app = aiohttp_web.Application()
fastapi_app = fastapi.FastAPI(
title=settings.app.title,
@ -77,7 +87,7 @@ class Application:
)
# Routes
fastapi_app.include_router(api_v1_handlers.health_router, prefix="/api/v1/health", tags=["health"])
fastapi_app.include_router(liveness_probe_handler, prefix="/api/v1/health", tags=["health"])
application = Application(
settings=settings,

View File

@ -4,19 +4,28 @@ import pydantic_settings
import lib.app.split_settings.utils as app_split_settings_utils
class DBSettings(pydantic_settings.BaseSettings):
"""Parent DB Settings Class."""
class PostgresSettings(pydantic_settings.BaseSettings):
"""Postgres settings."""
model_config = pydantic_settings.SettingsConfigDict(
env_file=app_split_settings_utils.ENV_PATH,
env_prefix="POSTGRES_",
env_file_encoding="utf-8",
extra="ignore",
)
# Connection settings
protocol: str
name: str
host: str
port: int
user: str
password: pydantic.SecretStr
driver: str = "postgresql+asyncpg"
db_name: str = "database_name"
host: str = "localhost"
port: int = 5432
user: str = "app"
password: pydantic.SecretStr = pydantic.Field(
default=..., validation_alias=pydantic.AliasChoices("password", "postgres_password")
)
# Enginge settings
pool_size: int = 10
# Engine settings
pool_size: int = 50
pool_pre_ping: bool = True
echo: bool = False
@ -27,29 +36,9 @@ class DBSettings(pydantic_settings.BaseSettings):
@property
def dsn(self) -> str:
password = self.password.get_secret_value() if isinstance(self.password, pydantic.SecretStr) else self.password
return f"{self.protocol}://{self.user}:{password}@{self.host}:{self.port}"
password = self.password.get_secret_value()
return f"{self.driver}://{self.user}:{password}@{self.host}:{self.port}"
@property
def dsn_as_safe_url(self) -> str:
return f"{self.protocol}://{self.user}:***@{self.host}:{self.port}"
class PostgresSettings(DBSettings):
"""Postgres settings."""
model_config = pydantic_settings.SettingsConfigDict(
env_file=app_split_settings_utils.ENV_PATH,
env_prefix="POSTGRES_",
env_file_encoding="utf-8",
extra="ignore",
)
protocol: str = "postgresql+asyncpg"
name: str = "database_name"
host: str = "localhost"
port: int = 5432
user: str = "app"
password: pydantic.SecretStr = pydantic.Field(
default=..., validation_alias=pydantic.AliasChoices("password", "postgres_password")
)
return f"{self.driver}://{self.user}:***@{self.host}:{self.port}"

View File

@ -5,6 +5,8 @@ import lib.app.split_settings.utils as app_split_settings_utils
class ProjectSettings(pydantic_settings.BaseSettings):
"""Project settings."""
model_config = pydantic_settings.SettingsConfigDict(
env_file=app_split_settings_utils.ENV_PATH,
env_file_encoding="utf-8",
@ -12,7 +14,8 @@ class ProjectSettings(pydantic_settings.BaseSettings):
)
debug: str = "false"
jwt_secret_key: pydantic.SecretStr = pydantic.Field(default=..., validation_alias="jwt_secret_key")
jwt_secret_key: str = pydantic.Field(default=..., validation_alias="jwt_secret_key")
jwt_algorithm: str = "HS256"
@pydantic.field_validator("debug")
def validate_debug(cls, v: str) -> bool:

View File

@ -1,3 +1,4 @@
from .postgres import get_async_session
from .httpx import get_async_http_session
from .postgres import AsyncPostgresClient
__all__ = ["get_async_session"]
__all__ = ["AsyncPostgresClient", "get_async_http_session"]

View File

@ -0,0 +1,16 @@
import contextlib
import typing
import httpx
@contextlib.asynccontextmanager
async def get_async_http_session(
settings: dict[str, typing.Any] | None = None
) -> typing.AsyncGenerator[httpx.AsyncClient, None]:
"""Async http client."""
if settings is None:
settings = {}
client = httpx.AsyncClient(**settings) # Insert your own settings here
async with client as ac:
yield ac

View File

@ -1,24 +1,30 @@
import sqlalchemy.ext.asyncio as sa_asyncio
import lib.app.split_settings as app_split_settings
import lib.app.settings as app_settings
async def get_async_session(
settings: app_split_settings.DBSettings,
) -> sa_asyncio.async_sessionmaker[sa_asyncio.AsyncSession]:
engine = sa_asyncio.create_async_engine(
url=settings.dsn,
pool_size=settings.pool_size,
pool_pre_ping=settings.pool_pre_ping,
echo=settings.echo,
class AsyncPostgresClient:
"""Async Postgres Client that return sessionmaker."""
def __init__(self, settings: app_settings.Settings) -> None:
self.settings = settings.postgres
self.async_enging = sa_asyncio.create_async_engine(
url=self.settings.dsn,
pool_size=self.settings.pool_size,
pool_pre_ping=self.settings.pool_pre_ping,
echo=self.settings.echo,
future=True,
)
def get_async_session(self) -> sa_asyncio.async_sessionmaker[sa_asyncio.AsyncSession]:
async_session = sa_asyncio.async_sessionmaker(
bind=engine,
autocommit=settings.auto_commit,
autoflush=settings.auto_flush,
expire_on_commit=settings.expire_on_commit,
bind=self.async_enging,
autocommit=self.settings.auto_commit,
autoflush=self.settings.auto_flush,
expire_on_commit=self.settings.expire_on_commit,
)
return async_session # noqa: RET504
async def dispose_callback(self) -> None:
await self.async_enging.dispose()

View File

@ -1,3 +1,4 @@
from .base_sqlalchemy import Base
from .base_sqlalchemy import Base, IdCreatedUpdatedBaseMixin
from .token import Token
__all__ = ["Base"]
__all__ = ["Base", "IdCreatedUpdatedBaseMixin", "Token"]

View File

@ -1,5 +1,36 @@
import uuid
import sqlalchemy
import sqlalchemy.dialects.postgresql
import sqlalchemy.ext.declarative
import sqlalchemy.orm
class Base(sqlalchemy.orm.DeclarativeBase):
"""Base class for all models."""
pass
class IdCreatedUpdatedBaseMixin(Base):
@sqlalchemy.ext.declarative.declared_attr
def uuid(cls):
return sqlalchemy.Column(
sqlalchemy.dialects.postgresql.UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
unique=True,
nullable=False,
)
@sqlalchemy.ext.declarative.declared_attr
def created_at(cls):
return sqlalchemy.Column(sqlalchemy.DateTime, server_default=sqlalchemy.sql.func.now())
@sqlalchemy.ext.declarative.declared_attr
def updated_at(cls):
return sqlalchemy.Column(sqlalchemy.DateTime, server_default=sqlalchemy.sql.func.now())
@sqlalchemy.ext.declarative.declared_attr.directive
def __tablename__(cls) -> str:
return cls.__name__.lower()

View File

@ -0,0 +1,9 @@
import uuid
import pydantic
# TODO: TBU
class Token(pydantic.BaseModel):
sub: uuid.UUID
exp: int | None = None

View File

@ -0,0 +1,25 @@
import fastapi
import fastapi.security
import jose
import jose.jwt
import pydantic
import lib.app.settings as app_settings
import lib.models as models
def get_token_data(
authorization: fastapi.security.HTTPAuthorizationCredentials = fastapi.Security(fastapi.security.HTTPBearer()),
) -> models.Token:
settings = app_settings.Settings()
token = authorization.credentials
try:
secret_key = settings.project.jwt_secret_key
payload = jose.jwt.decode(token, secret_key, algorithms=[settings.project.jwt_algorithm])
return models.Token(**payload)
except (jose.JWTError, pydantic.ValidationError) as error:
raise fastapi.HTTPException(
status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
) from error

View File

@ -162,6 +162,17 @@ d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2023.7.22"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"},
{file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"},
]
[[package]]
name = "click"
version = "8.1.7"
@ -361,6 +372,50 @@ files = [
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
[[package]]
name = "httpcore"
version = "0.18.0"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"},
{file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"},
]
[package.dependencies]
anyio = ">=3.0,<5.0"
certifi = "*"
h11 = ">=0.13,<0.15"
sniffio = "==1.*"
[package.extras]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]]
name = "httpx"
version = "0.25.0"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
{file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"},
{file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"},
]
[package.dependencies]
certifi = "*"
httpcore = ">=0.18.0,<0.19.0"
idna = "*"
sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
[[package]]
name = "idna"
version = "3.4"
@ -1357,4 +1412,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "f9ae9c843527beb7b8c58cae8cfbf5a6d4a2ef89d7b0a83f5fe30d9f8956fdc8"
content-hash = "a1f51871ee88f7c8503f57105be988ea4ece9f41671d11a4464c382057acf3e7"

View File

@ -24,6 +24,7 @@ alembic = "^1.12.0"
asyncpg = "^0.28.0"
fastapi = "0.103.1"
greenlet = "^2.0.2"
httpx = "^0.25.0"
orjson = "^3.9.7"
psycopg2 = "^2.9.7"
pydantic = {extras = ["email"], version = "^2.3.0"}

View File

@ -1,2 +0,0 @@
.venv
.env

View File

@ -1,11 +0,0 @@
LOG_LEVEL_HANDLERS=INFO
LOG_LEVEL_LOGGERS=INFO
LOG_LEVEL_ROOT=INFO
API_PORT=8000
NGINX_PORT=80
PROJET_NAME="FastAPI Template"
PROJECT_DESCRIPTION="FastAPI Template Project using DDD"
PROJECT_VERSION=0.0.1

View File

@ -1,11 +0,0 @@
{
"*.py": [
".venv/bin/python -m black --check",
".venv/bin/python -m isort --check",
".venv/bin/python -m sort_all",
".venv/bin/python -m pyupgrade --py311-plus --keep-runtime-typing",
".venv/bin/python -m pylint",
"../../node_modules/.bin/pyright"
],
"*.toml": [".venv/bin/toml-sort --check"]
}

View File

@ -1,18 +0,0 @@
FROM python:3.11
RUN apt-get update
WORKDIR /opt/app
ENV PYTHONPATH '/opt/app'
COPY pyproject.toml ./
COPY poetry.lock ./
RUN apt-get update \
&& pip install poetry \
&& poetry config virtualenvs.create false \
&& poetry install --no-dev
COPY backend .
# CMD ["python", "-m", "bin"]

View File

@ -1,3 +0,0 @@
include ../../common_makefile.mk
PROJECT_FOLDERS = backend tests

View File

@ -1,3 +0,0 @@
# Example monorepo package
...

View File

@ -1,36 +0,0 @@
import logging
import faker
import backend.user.handlers as user_handlers
import backend.user.repositories as user_repositories
import backend.user.services as user_services
logger = logging.getLogger(__name__)
class App:
def __init__(self):
self._faker_client = faker.Faker()
self._user_faker_client2 = user_repositories.UserFakerClient2(self._faker_client)
self._user_service = user_services.UserService(self._user_faker_client2)
self._handler1 = user_handlers.UserHandler1(self._user_service)
self._handler2 = user_handlers.UserHandler2(self._user_service)
logging.basicConfig(level=logging.INFO)
def run(self):
logger.info(self._handler1.get_one())
logger.info(self._handler2.get_one())
def close(self):
logger.info("Closing app...")
del self._faker_client
if __name__ == "__main__":
app = App()
try:
app.run()
finally:
app.close()

View File

@ -1,20 +0,0 @@
import backend.user.services as user_services
import backend.utils.http as http_utils
class UserHandler1:
def __init__(self, user_service: user_services.UserService):
self._user_service = user_service
def get_one(self) -> http_utils.Response:
user = self._user_service.get_one(entity_id=1)
return http_utils.get_response_from_dataclass(user)
class UserHandler2:
def __init__(self, user_service: user_services.UserService):
self._user_service = user_service
def get_one(self) -> http_utils.Response:
user = self._user_service.get_one(entity_id=2)
return http_utils.get_response_from_dataclass(user)

View File

@ -1,12 +0,0 @@
import dataclasses
@dataclasses.dataclass
class User:
id: int # pylint: disable=C0103
name: str
__all__ = [
"User",
]

View File

@ -1,23 +0,0 @@
import faker
class UserFakerClient:
def __init__(self, faker_client: faker.Faker):
self._faker_client = faker_client
def get_name(self) -> str:
return self._faker_client.name()
def get_email(self) -> str:
return self._faker_client.email()
class UserFakerClient2:
def __init__(self, faker_client: faker.Faker):
self._faker_client = faker_client
def get_name(self) -> str:
return self._faker_client.full_name()
def get_email(self) -> str:
return self._faker_client.email()

View File

@ -1,16 +0,0 @@
import typing
import backend.user.models as models
class UserClientProtocol(typing.Protocol):
def get_name(self) -> str:
...
class UserService:
def __init__(self, user_client: UserClientProtocol):
self._user_client = user_client
def get_one(self, entity_id: int) -> models.User:
return models.User(id=entity_id, name=self._user_client.get_name())

View File

@ -1,20 +0,0 @@
import dataclasses
import json
import typing
class Handler(typing.Protocol):
def __call__(self) -> "Response":
...
@dataclasses.dataclass
class Response:
code: int
body: str
def get_response_from_dataclass(body: object) -> Response:
assert dataclasses.is_dataclass(body), "body must be a dataclass"
return Response(code=200, body=json.dumps(dataclasses.asdict(body)))

View File

@ -1,29 +0,0 @@
version: "3"
services:
api:
build:
context: .
ports:
- "${API_PORT}:${API_PORT}"
expose:
- ${API_PORT}
env_file:
- .env
nginx:
image: nginx:1.25.1
restart: always
env_file:
- .env
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/templates:/etc/nginx/templates
ports:
- "${NGINX_PORT}:${NGINX_PORT}"
depends_on:
- api
networks:
default:
driver: bridge

View File

@ -1,38 +0,0 @@
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
server_tokens off;
sendfile on;
tcp_nodelay on;
tcp_nopush on;
client_max_body_size 200m;
gzip on;
gzip_comp_level 3;
gzip_min_length 1000;
gzip_types
text/plain
text/css
application/json
application/x-javascript
text/xml
text/javascript;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
include conf.d/api.conf;
include conf.d/rabbitmq.conf;
}

View File

@ -1,11 +0,0 @@
server {
listen ${NGINX_PORT} default_server;
listen [::]:${NGINX_PORT} default_server;
server_name _;
location /api {
proxy_pass http://api:${API_PORT}/api;
proxy_set_header X-Request-Id $request_id;
}
}

View File

@ -1,482 +0,0 @@
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
[[package]]
name = "astroid"
version = "2.15.6"
description = "An abstract syntax tree for Python with inference support."
optional = false
python-versions = ">=3.7.2"
files = [
{file = "astroid-2.15.6-py3-none-any.whl", hash = "sha256:389656ca57b6108f939cf5d2f9a2a825a3be50ba9d589670f393236e0a03b91c"},
{file = "astroid-2.15.6.tar.gz", hash = "sha256:903f024859b7c7687d7a7f3a3f73b17301f8e42dfd9cc9df9d4418172d3e2dbd"},
]
[package.dependencies]
lazy-object-proxy = ">=1.4.0"
wrapt = {version = ">=1.14,<2", markers = "python_version >= \"3.11\""}
[[package]]
name = "black"
version = "23.9.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"},
{file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"},
{file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"},
{file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"},
{file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"},
{file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"},
{file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"},
{file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"},
{file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"},
{file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"},
{file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"},
{file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"},
{file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"},
{file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"},
{file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "dill"
version = "0.3.7"
description = "serialize all of Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "dill-0.3.7-py3-none-any.whl", hash = "sha256:76b122c08ef4ce2eedcd4d1abd8e641114bfc6c2867f49f3c41facf65bf19f5e"},
{file = "dill-0.3.7.tar.gz", hash = "sha256:cc1c8b182eb3013e24bd475ff2e9295af86c1a38eb1aff128dac8962a9ce3c03"},
]
[package.extras]
graph = ["objgraph (>=1.7.2)"]
[[package]]
name = "faker"
version = "19.6.1"
description = "Faker is a Python package that generates fake data for you."
optional = false
python-versions = ">=3.8"
files = [
{file = "Faker-19.6.1-py3-none-any.whl", hash = "sha256:64c8513c53c3a809075ee527b323a0ba61517814123f3137e4912f5d43350139"},
{file = "Faker-19.6.1.tar.gz", hash = "sha256:5d6b7880b3bea708075ddf91938424453f07053a59f8fa0453c1870df6ff3292"},
]
[package.dependencies]
python-dateutil = ">=2.4"
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
optional = false
python-versions = ">=3.7"
files = [
{file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "isort"
version = "5.12.0"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
]
[package.extras]
colors = ["colorama (>=0.4.3)"]
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
name = "lazy-object-proxy"
version = "1.9.0"
description = "A fast and thorough lazy object proxy."
optional = false
python-versions = ">=3.7"
files = [
{file = "lazy-object-proxy-1.9.0.tar.gz", hash = "sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-win32.whl", hash = "sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455"},
{file = "lazy_object_proxy-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-win32.whl", hash = "sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586"},
{file = "lazy_object_proxy-1.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-win32.whl", hash = "sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734"},
{file = "lazy_object_proxy-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-win32.whl", hash = "sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82"},
{file = "lazy_object_proxy-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-win32.whl", hash = "sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821"},
{file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"},
]
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "packaging"
version = "23.1"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.7"
files = [
{file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
{file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
]
[[package]]
name = "pathspec"
version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.7"
files = [
{file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"},
{file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"},
]
[[package]]
name = "platformdirs"
version = "3.10.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.10.0-py3-none-any.whl", hash = "sha256:d7c24979f292f916dc9cbf8648319032f551ea8c49a4c9bf2fb556a02070ec1d"},
{file = "platformdirs-3.10.0.tar.gz", hash = "sha256:b45696dab2d7cc691a3226759c0d3b00c47c8b6e293d96f6436f733303f77f6d"},
]
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"]
[[package]]
name = "pluggy"
version = "1.3.0"
description = "plugin and hook calling mechanisms for python"
optional = false
python-versions = ">=3.8"
files = [
{file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"},
{file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"},
]
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pylint"
version = "2.17.5"
description = "python code static checker"
optional = false
python-versions = ">=3.7.2"
files = [
{file = "pylint-2.17.5-py3-none-any.whl", hash = "sha256:73995fb8216d3bed149c8d51bba25b2c52a8251a2c8ac846ec668ce38fab5413"},
{file = "pylint-2.17.5.tar.gz", hash = "sha256:f7b601cbc06fef7e62a754e2b41294c2aa31f1cb659624b9a85bcba29eaf8252"},
]
[package.dependencies]
astroid = ">=2.15.6,<=2.17.0-dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = {version = ">=0.3.6", markers = "python_version >= \"3.11\""}
isort = ">=4.2.5,<6"
mccabe = ">=0.6,<0.8"
platformdirs = ">=2.2.0"
tomlkit = ">=0.10.1"
[package.extras]
spelling = ["pyenchant (>=3.2,<4.0)"]
testutils = ["gitpython (>3)"]
[[package]]
name = "pytest"
version = "7.4.2"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
{file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"},
{file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
files = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
[package.dependencies]
six = ">=1.5"
[[package]]
name = "pyupgrade"
version = "3.11.1"
description = "A tool to automatically upgrade syntax for newer versions."
optional = false
python-versions = ">=3.8.1"
files = [
{file = "pyupgrade-3.11.1-py2.py3-none-any.whl", hash = "sha256:6e9dd362394b3068123e06ca268de5845d41e2bb29f387b38323cc1009fb3100"},
{file = "pyupgrade-3.11.1.tar.gz", hash = "sha256:3e6c7689d2f3ae418c6a60ee981477fe9130eccaed3e33dac6c21274cf7d45f4"},
]
[package.dependencies]
tokenize-rt = ">=5.2.0"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "sort-all"
version = "1.2.0"
description = "Automatically Sort __all__ records alphabetically"
optional = false
python-versions = ">=3.8"
files = [
{file = "sort_all-1.2.0-py3-none-any.whl", hash = "sha256:ccc0fc7191a486ff826cb4d21c2b67d93f9d9cb5eb72e8d572d017d50d4eca88"},
{file = "sort_all-1.2.0.tar.gz", hash = "sha256:1eb6a91cc61f36bd48d697f687377c6eb67b4ef98e2850fc65130502bae945d8"},
]
[package.dependencies]
tokenize-rt = ">=3.0.1"
[[package]]
name = "tokenize-rt"
version = "5.2.0"
description = "A wrapper around the stdlib `tokenize` which roundtrips."
optional = false
python-versions = ">=3.8"
files = [
{file = "tokenize_rt-5.2.0-py2.py3-none-any.whl", hash = "sha256:b79d41a65cfec71285433511b50271b05da3584a1da144a0752e9c621a285289"},
{file = "tokenize_rt-5.2.0.tar.gz", hash = "sha256:9fe80f8a5c1edad2d3ede0f37481cc0cc1538a2f442c9c2f9e4feacd2792d054"},
]
[[package]]
name = "toml-sort"
version = "0.23.1"
description = "Toml sorting library"
optional = false
python-versions = ">=3.7,<4.0"
files = [
{file = "toml_sort-0.23.1-py3-none-any.whl", hash = "sha256:69ae60de9c4d67478533697eb4119092e2b30ddffe5ca09bbad3912905c935a0"},
{file = "toml_sort-0.23.1.tar.gz", hash = "sha256:833728c48b0f8d509aecd9ae8347768ca3a9332977b32c9fd2002932f0eb9c90"},
]
[package.dependencies]
tomlkit = ">=0.11.2"
[[package]]
name = "tomlkit"
version = "0.12.1"
description = "Style preserving TOML library"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomlkit-0.12.1-py3-none-any.whl", hash = "sha256:712cbd236609acc6a3e2e97253dfc52d4c2082982a88f61b640ecf0817eab899"},
{file = "tomlkit-0.12.1.tar.gz", hash = "sha256:38e1ff8edb991273ec9f6181244a6a391ac30e9f5098e7535640ea6be97a7c86"},
]
[[package]]
name = "wrapt"
version = "1.15.0"
description = "Module for decorators, wrappers and monkey patching."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
files = [
{file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"},
{file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"},
{file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"},
{file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"},
{file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"},
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"},
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"},
{file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"},
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"},
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"},
{file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"},
{file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"},
{file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"},
{file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"},
{file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"},
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"},
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"},
{file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"},
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"},
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"},
{file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"},
{file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"},
{file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"},
{file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"},
{file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"},
{file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"},
{file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"},
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"},
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"},
{file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"},
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"},
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"},
{file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"},
{file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"},
{file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"},
{file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"},
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"},
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"},
{file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"},
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"},
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"},
{file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"},
{file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"},
{file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"},
{file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"},
{file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"},
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"},
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"},
{file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"},
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"},
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"},
{file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"},
{file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"},
{file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"},
{file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"},
{file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"},
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"},
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"},
{file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"},
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"},
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"},
{file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"},
{file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"},
{file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"},
{file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"},
{file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"},
]
[metadata]
lock-version = "2.0"
python-versions = "^3.11"
content-hash = "e25f13be31f469d85a4a042b9d4c52f6828b417fcb114065ccf94b789353e743"

View File

@ -1,3 +0,0 @@
[virtualenvs]
create = true
in-project = true

View File

@ -1,74 +0,0 @@
[build-system]
build-backend = "poetry.core.masonry.api"
requires = ["poetry-core>=1.0.0"]
[tool.black]
known_first_party = ["backend", "tests"]
line-length = 120
target-version = ["py311"]
[tool.isort]
line_length = 120
profile = "black"
[tool.poetry]
authors = ["name <name@email.com>"]
description = "FastAPI Template Project using DDD"
name = "FastAPI Template"
version = "0.0.1"
[tool.poetry.dependencies]
black = "^23.9.1"
faker = "^19.6.1"
isort = "^5.12.0"
pylint = "^2.17.5"
pytest = "^7.4.2"
python = "^3.11"
pyupgrade = "^3.11.0"
sort-all = "^1.2.0"
toml-sort = "^0.23.1"
[tool.poetry.dev-dependencies]
[tool.pylint]
disable = [
"broad-except",
"consider-using-from-import",
"consider-using-sys-exit",
"duplicate-code",
"missing-docstring",
"protected-access",
"too-few-public-methods"
]
ignore-paths = ["^.*venv/.*$"]
max-line-length = 120
recursive = true
[tool.pylint.basic]
argument-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
class-attribute-rgx = "^_{0,2}[a-zA-Z][a-zA-Z0-9_]*$"
variable-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
[tool.pylint.typecheck]
signature-mutators = "click.decorators.option"
[tool.pyright]
exclude = ["**/__pycache__"]
include = ["backend", "tests"]
pythonPlatform = "All"
pythonVersion = "3.11"
reportConstantRedefinition = "none"
reportMissingTypeStubs = "warning"
reportPrivateUsage = "information"
reportPropertyTypeMismatch = "warning"
reportUninitializedInstanceVariable = "warning"
reportUnknownMemberType = "none"
reportUnnecessaryTypeIgnoreComment = "warning"
typeCheckingMode = "strict"
useLibraryCodeForTypes = true
venv = ".venv"
venvPath = '.'
[tool.tomlsort]
all = true
in_place = true

View File

@ -1,2 +0,0 @@
.venv/
tests/

View File

@ -1,13 +0,0 @@
# Python dependencies
.venv/
# Python cache
__pycache__/
.pytest_cache/
# Coverage reports
/.coverage
htmlcov/
# Environment variables
.env

View File

@ -1,11 +0,0 @@
{
"*.py": [
".venv/bin/python -m black --check",
".venv/bin/python -m isort --check",
".venv/bin/python -m sort_all",
".venv/bin/python -m pyupgrade --py311-plus",
".venv/bin/python -m pylint",
"../node_modules/.bin/pyright"
],
"*.toml": [".venv/bin/toml-sort --check"]
}

View File

@ -1,12 +0,0 @@
# Python dependencies
.venv/
# Python cache
__pycache__/
.pytest_cache/
# Coverage reports
/.coverage
# Environment variables
.env

View File

@ -1,19 +0,0 @@
FROM ghcr.io/yp-middle-python-24/python-dev:3.10-jammy-0.0.1 AS builder
RUN mkdir --parents /opt/app
COPY pyproject.toml /opt/app/pyproject.toml
COPY poetry.lock /opt/app/poetry.lock
COPY poetry.toml /opt/app/poetry.toml
WORKDIR /opt/app
RUN poetry install --no-dev
FROM ghcr.io/yp-middle-python-24/python:3.10-jammy-0.0.1 AS runtime
RUN mkdir --parents /opt/app
COPY --from=builder /opt/app/.venv /opt/app/.venv
COPY bin /opt/app/bin
COPY lib /opt/app/lib
WORKDIR /opt/app
CMD [".venv/bin/python", "-m", "bin.main"]

View File

@ -1,3 +0,0 @@
include ../../common_makefile.mk
PROJECT_FOLDERS = bin lib tests

View File

@ -1,28 +0,0 @@
# Python Service Example Backend
Python Service Example Backend
## Development
### Global dependencies
- poetry
### Makefile commands
- `make init` - Initialize service
- `make lint` - Lint service
- `make lint-fix` - Auto-fix service
- `make test` - Test service
- `make clean` - Clean up service
- `make dev-server-start` - Start dev server
- `make test-coverage-run` - Collect test coverage data
- `make test-coverage-report` - Show coverage report in console
- `make test-coverage-html` - Prepare and show coverage report in browser
- `make ci-image-build` - Build production container
- `make ci-image-push` - Push production container to yccr
### Environment variables
- `APP_ENV` - Application environment (development, production, etc.)
- `APP_VERSION` - Application version

View File

@ -1,37 +0,0 @@
import asyncio
import logging
import os
import lib.app as app
logger = logging.getLogger(__name__)
async def run() -> None:
settings = app.Settings()
application = app.Application.from_settings(settings)
try:
await application.start()
finally:
await application.dispose()
def main() -> None:
try:
asyncio.run(run())
exit(os.EX_OK)
except SystemExit:
exit(os.EX_OK)
except app.ApplicationError:
exit(os.EX_SOFTWARE)
except KeyboardInterrupt:
logger.info("Exited with keyboard interruption")
exit(os.EX_OK)
except BaseException:
logger.exception("Unexpected error occurred")
exit(os.EX_SOFTWARE)
if __name__ == "__main__":
main()

View File

@ -1,5 +0,0 @@
from .liveness_probe import LivenessProbeHandler
__all__ = [
"LivenessProbeHandler",
]

View File

@ -1,17 +0,0 @@
import json
import aiohttp.web as aiohttp_web
import lib.utils.aiohttp as aiohttp_utils
class LivenessProbeHandler(aiohttp_utils.HandlerProtocol):
async def process(self, request: aiohttp_web.Request) -> aiohttp_web.Response:
return aiohttp_web.Response(
status=200,
body=json.dumps(obj={"status": "healthy"}),
)
__all__ = [
"LivenessProbeHandler",
]

View File

@ -1,11 +0,0 @@
from .app import Application
from .errors import ApplicationError, DisposeError, StartServerError
from .settings import Settings
__all__ = [
"Application",
"ApplicationError",
"DisposeError",
"Settings",
"StartServerError",
]

View File

@ -1,134 +0,0 @@
import asyncio
import dataclasses
import logging
import typing
import aiohttp.web as aiohttp_web
import lib.api.rest.v1.health as health_handlers
import lib.app.errors as app_errors
import lib.app.settings as app_settings
import typing_extensions
logger = logging.getLogger(__name__)
@dataclasses.dataclass
class DisposableResource:
name: str
dispose_callback: typing.Awaitable[typing.Any]
class Application:
def __init__(
self,
settings: app_settings.Settings,
aio_app: aiohttp_web.Application,
disposable_resources: typing.Sequence[DisposableResource],
) -> None:
self._settings = settings
self._aio_app = aio_app
self._disposable_resources = disposable_resources
@classmethod
def from_settings(cls, settings: app_settings.Settings) -> typing_extensions.Self:
# Logging
logging.basicConfig(
level=settings.LOGS_MIN_LEVEL,
format=settings.LOGS_FORMAT,
)
logger.info("Initializing application")
disposable_resources = []
# Global clients
logger.info("Initializing global clients")
# Clients
logger.info("Initializing clients")
# Repositories
logger.info("Initializing repositories")
# Caches
logger.info("Initializing caches")
# Services
logger.info("Initializing services")
# Handlers
logger.info("Initializing handlers")
liveness_probe_handler = health_handlers.LivenessProbeHandler()
logger.info("Creating application")
aio_app = aiohttp_web.Application()
# Routes
aio_app.add_routes(
[
aiohttp_web.get(
"/api/v1/health/liveness",
liveness_probe_handler.process,
),
]
)
application = Application(
settings=settings,
aio_app=aio_app,
disposable_resources=disposable_resources,
)
logger.info("Initializing application finished")
return application
async def start(self) -> None:
logger.info("Discord server is starting")
try:
await aiohttp_web._run_app(
app=self._aio_app,
host=self._settings.SERVER_HOST,
port=self._settings.SERVER_PORT,
)
except asyncio.CancelledError:
logger.info("HTTP server has been interrupted")
except BaseException as unexpected_error:
logger.exception("HTTP server failed to start")
raise app_errors.StartServerError(
"HTTP server failed to start"
) from unexpected_error
async def dispose(self) -> None:
logger.info("Application is shutting down...")
dispose_errors = []
for resource in self._disposable_resources:
logger.info("Disposing %s...", resource.name)
try:
await resource.dispose_callback
except Exception as unexpected_error:
dispose_errors.append(unexpected_error)
logger.exception("Failed to dispose %s", resource.name)
else:
logger.info("%s has been disposed", resource.name)
if len(dispose_errors) != 0:
logger.error("Application has shut down with errors")
raise app_errors.DisposeError(
"Application has shut down with errors, see logs above"
)
logger.info("Application has successfully shut down")
__all__ = [
"Application",
]

View File

@ -1,22 +0,0 @@
import typing
class ApplicationError(Exception):
def __init__(self, message: str, *args: typing.Any) -> None:
super().__init__(*args)
self.message = message
class DisposeError(ApplicationError):
pass
class StartServerError(ApplicationError):
pass
__all__ = [
"ApplicationError",
"DisposeError",
"StartServerError",
]

View File

@ -1,32 +0,0 @@
import typing
import pydantic
LogLevel = typing.Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
class Settings(pydantic.BaseSettings):
# App
APP_ENV: str = "development"
APP_NAME: str = "discord-chatbot-backend"
APP_VERSION: str = "0.0.1"
# Logging
LOGS_MIN_LEVEL: LogLevel = "DEBUG"
LOGS_FORMAT: str = "%(asctime)s | %(name)s | %(levelname)s | %(message)s"
# Server
SERVER_HOST: str = "localhost"
SERVER_PORT: int = 8080
@property
def is_development(self) -> bool:
return self.APP_ENV == "development"
__all__ = [
"Settings",
]

View File

@ -1,5 +0,0 @@
from .types import HandlerProtocol
__all__ = [
"HandlerProtocol",
]

View File

@ -1,5 +0,0 @@
from .handler import HandlerProtocol
__all__ = [
"HandlerProtocol",
]

View File

@ -1,13 +0,0 @@
import typing
import aiohttp.web as aiohttp_web
class HandlerProtocol(typing.Protocol):
async def process(self, request: aiohttp_web.Request) -> aiohttp_web.Response:
...
__all__ = [
"HandlerProtocol",
]

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +0,0 @@
[virtualenvs]
create = true
in-project = true

View File

@ -1,121 +0,0 @@
[build-system]
build-backend = "poetry.core.masonry.api"
requires = ["poetry-core>=1.0.0"]
[tool.black]
line-length = 120
target-version = ["py311"]
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"raise NotImplementedError",
"if __name__ == .__main__.:",
"class .*Protocol\\):",
"@(abc\\.)?abstractmethod",
"if typing.TYPE_CHECKING:",
]
[tool.coverage.run]
include = ["bin/*", "lib/*"]
[tool.isort]
known_first_party = ["bin", "lib", "tests"]
line_length = 120
profile = "black"
py_version = 311
[tool.poetry]
authors = ["ovsds <ovsds@yandex-team.ru>"]
description = "Python Service Example Backend"
name = "python-service-example-backend"
version = "0.0.1"
[tool.poetry.dependencies]
aiohttp = "3.8.4"
pydantic = {extras = ["dotenv"], version = "1.10.2"}
python = "~3.11"
typing-extensions = "4.5.0"
[tool.poetry.dev-dependencies]
black = "22.6.0"
coverage = "6.5.0"
isort = "5.10.1"
pylint = "2.14.5"
pylint-pydantic = "0.1.4"
pylint-pytest = "1.1.2"
pytest = "7.1.2"
pytest-asyncio = "0.19.0"
pytest-mock = "3.8.2"
pyupgrade = "3.1.0"
sort-all = "1.2.0"
toml-sort = "0.20.0"
[tool.pylint]
disable = [
"broad-except",
"consider-using-from-import",
"consider-using-sys-exit",
"duplicate-code",
"fixme",
"missing-docstring",
"no-member",
"protected-access",
"too-few-public-methods",
"too-many-instance-attributes",
"too-many-locals",
"too-many-statements",
"unnecessary-ellipsis",
]
extension-pkg-allow-list = [
"pydantic", # https://github.com/samuelcolvin/pydantic/issues/1961
]
ignore-paths = [
"^.*venv/.*$",
]
load-plugins = [
"pylint_pydantic",
"pylint_pytest",
]
max-args = 10
max-line-length = 120
recursive = true
[tool.pylint.basic]
argument-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
attr-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
class-attribute-rgx = "^_{0,2}[a-zA-Z][a-zA-Z0-9_]*$"
variable-rgx = "^_{0,2}[a-z][a-z0-9_]*$"
[tool.pyright]
exclude = [
"**/__pycache__",
]
include = [
"bin",
"lib",
"tests",
]
pythonPlatform = "All"
pythonVersion = "3.11"
reportConstantRedefinition = "none"
reportMissingTypeStubs = "warning"
reportPrivateUsage = "information"
reportPropertyTypeMismatch = "warning"
reportUninitializedInstanceVariable = "warning"
reportUnknownMemberType = "none"
reportUnnecessaryTypeIgnoreComment = "warning"
reportUntypedFunctionDecorator = "warning"
typeCheckingMode = "strict"
useLibraryCodeForTypes = true
venv = ".venv"
venvPath = '.'
[tool.pytest.ini_options]
pythonpath = "."
[tool.tomlsort]
all = true
ignore_case = true
in_place = true