Merge branch 'master' into VORKOUT-7

This commit is contained in:
TheNoxium 2025-06-05 13:04:25 +05:00
commit 48d52bf903
34 changed files with 909 additions and 322 deletions

View File

@ -49,3 +49,12 @@ install:
%:: %::
echo $(MESSAGE) echo $(MESSAGE)
format-api:
cd api && \
poetry run ruff format .
check-api:
cd api && \
poetry run ruff format . --check

View File

@ -21,7 +21,6 @@ def bind_routes(application: FastAPI, setting: DefaultSettings) -> None:
application.include_router(route, prefix=setting.PATH_PREFIX) application.include_router(route, prefix=setting.PATH_PREFIX)
def get_app() -> FastAPI: def get_app() -> FastAPI:
"""Creates application and all dependable objects.""" """Creates application and all dependable objects."""
loguru.logger.remove() loguru.logger.remove()
@ -80,7 +79,6 @@ app.add_middleware(
allow_credentials=True, allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS", "DELETE", "PUT"], allow_methods=["GET", "POST", "OPTIONS", "DELETE", "PUT"],
allow_headers=["*"], allow_headers=["*"],
) )
app.add_middleware(MiddlewareAccessTokenValidadtion) app.add_middleware(MiddlewareAccessTokenValidadtion)

View File

@ -44,16 +44,11 @@ class DefaultSettings(BaseSettings):
REDIS_DB: int = int(environ.get("REDIS_DB", "0")) REDIS_DB: int = int(environ.get("REDIS_DB", "0"))
REDIS_PASSWORD: str = environ.get("REDIS_PASSWORD", "hackme") REDIS_PASSWORD: str = environ.get("REDIS_PASSWORD", "hackme")
SECRET_KEY: str = environ.get("SECRET_KEY", "secret") SECRET_KEY: str = environ.get("SECRET_KEY", "secret")
ALGORITHM: str = environ.get("ALGORITHM", "HS256") ALGORITHM: str = environ.get("ALGORITHM", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES: int = int( ACCESS_TOKEN_EXPIRE_MINUTES: int = int(environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", 600))
environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", 600)
)
REFRESH_TOKEN_EXPIRE_DAYS: int = int( REFRESH_TOKEN_EXPIRE_DAYS: int = int(environ.get("REFRESH_TOKEN_EXPIRE_DAYS_LONG", 365))
environ.get("REFRESH_TOKEN_EXPIRE_DAYS_LONG", 365)
)
@cached_property @cached_property
def database_settings(self) -> dict: def database_settings(self) -> dict:

View File

@ -8,18 +8,16 @@ import asyncio
import sqlalchemy import sqlalchemy
from loguru import logger from loguru import logger
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine
from sqlalchemy import URL,create_engine, text from sqlalchemy import URL, create_engine, text
from api.config import get_settings from api.config import get_settings
from api.config.default import DbCredentialsSchema from api.config.default import DbCredentialsSchema
class SessionManager: class SessionManager:
engines: Any engines: Any
def __init__(self, database_uri=get_settings().database_uri) -> None: def __init__(self, database_uri=get_settings().database_uri) -> None:
self.database_uri = database_uri self.database_uri = database_uri
self.refresh(database_uri) self.refresh(database_uri)
@ -44,9 +42,11 @@ class SessionManager:
pool_size=get_settings().CONNECTION_POOL_SIZE, pool_size=get_settings().CONNECTION_POOL_SIZE,
max_overflow=get_settings().CONNECTION_OVERFLOW, max_overflow=get_settings().CONNECTION_OVERFLOW,
) )
def get_engine_by_db_uri(self, database_uri) -> AsyncEngine: def get_engine_by_db_uri(self, database_uri) -> AsyncEngine:
return self.engines[database_uri] return self.engines[database_uri]
@contextlib.asynccontextmanager @contextlib.asynccontextmanager
async def get_connection( async def get_connection(
database_uri=None, database_uri=None,
@ -58,6 +58,7 @@ async def get_connection(
async with engine.connect() as conn: async with engine.connect() as conn:
yield conn yield conn
async def get_connection_dep() -> AsyncConnection: async def get_connection_dep() -> AsyncConnection:
async with get_connection() as conn: async with get_connection() as conn:
yield conn yield conn

View File

@ -56,10 +56,7 @@ async def get_user_by_id(connection: AsyncConnection, id: int) -> Optional[User]
""" """
Получает юзера по id. Получает юзера по id.
""" """
query = ( query = select(account_table).where(account_table.c.id == id)
select(account_table)
.where(account_table.c.id == id)
)
user_db_cursor = await connection.execute(query) user_db_cursor = await connection.execute(query)
user_db = user_db_cursor.one_or_none() user_db = user_db_cursor.one_or_none()
@ -68,8 +65,11 @@ async def get_user_by_id(connection: AsyncConnection, id: int) -> Optional[User]
return None return None
user_data = { user_data = {
column.name: (getattr(user_db, column.name).name if isinstance( column.name: (
getattr(user_db, column.name), Enum) else getattr(user_db, column.name)) getattr(user_db, column.name).name
if isinstance(getattr(user_db, column.name), Enum)
else getattr(user_db, column.name)
)
for column in account_table.columns for column in account_table.columns
} }
@ -80,10 +80,7 @@ async def get_user_by_login(connection: AsyncConnection, login: str) -> Optional
""" """
Получает юзера по login. Получает юзера по login.
""" """
query = ( query = select(account_table).where(account_table.c.login == login)
select(account_table)
.where(account_table.c.login == login)
)
user_db_cursor = await connection.execute(query) user_db_cursor = await connection.execute(query)
user_db = user_db_cursor.one_or_none() user_db = user_db_cursor.one_or_none()
@ -92,8 +89,11 @@ async def get_user_by_login(connection: AsyncConnection, login: str) -> Optional
return None return None
user_data = { user_data = {
column.name: (getattr(user_db, column.name).name if isinstance( column.name: (
getattr(user_db, column.name), Enum) else getattr(user_db, column.name)) getattr(user_db, column.name).name
if isinstance(getattr(user_db, column.name), Enum)
else getattr(user_db, column.name)
)
for column in account_table.columns for column in account_table.columns
} }
@ -104,11 +104,7 @@ async def update_user_by_id(connection: AsyncConnection, update_values, user) ->
""" """
Вносит изменеия в нужное поле таблицы account_table. Вносит изменеия в нужное поле таблицы account_table.
""" """
await connection.execute( await connection.execute(account_table.update().where(account_table.c.id == user.id).values(**update_values))
account_table.update()
.where(account_table.c.id == user.id)
.values(**update_values)
)
await connection.commit() await connection.commit()
@ -126,7 +122,7 @@ async def create_user(connection: AsyncConnection, user: User, creator_id: int)
meta=user.meta, meta=user.meta,
creator_id=creator_id, creator_id=creator_id,
created_at=datetime.now(timezone.utc), created_at=datetime.now(timezone.utc),
status=user.status.value status=user.status.value,
) )
await connection.execute(query) await connection.execute(query)

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from sqlalchemy import select, update from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from enum import Enum from enum import Enum
@ -11,16 +11,14 @@ from api.schemas.account.account_keyring import AccountKeyring
from api.utils.key_id_gen import KeyIdGenerator from api.utils.key_id_gen import KeyIdGenerator
from datetime import datetime, timezone from datetime import datetime, timezone
async def get_user(connection: AsyncConnection, login: str) -> Optional[User]: async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
query = ( query = (
select(account_table, account_keyring_table) select(account_table, account_keyring_table)
.join(account_keyring_table, account_table.c.id == account_keyring_table.c.owner_id) .join(account_keyring_table, account_table.c.id == account_keyring_table.c.owner_id)
.where(account_table.c.login == login, .where(account_table.c.login == login, account_keyring_table.c.key_type == KeyType.PASSWORD)
account_keyring_table.c.key_type == KeyType.PASSWORD)
) )
user_db_cursor = await connection.execute(query) user_db_cursor = await connection.execute(query)
@ -29,16 +27,21 @@ async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
if not user_db: if not user_db:
return None, None return None, None
user_data = { user_data = {
column.name: (getattr(user_db, column.name).name if isinstance( column.name: (
getattr(user_db, column.name), Enum) else getattr(user_db, column.name)) getattr(user_db, column.name).name
if isinstance(getattr(user_db, column.name), Enum)
else getattr(user_db, column.name)
)
for column in account_table.columns for column in account_table.columns
} }
password_data = { password_data = {
column.name: (getattr(user_db, column.name).name if isinstance( column.name: (
getattr(user_db, column.name), Enum) else getattr(user_db, column.name)) getattr(user_db, column.name).name
if isinstance(getattr(user_db, column.name), Enum)
else getattr(user_db, column.name)
)
for column in account_keyring_table.columns for column in account_keyring_table.columns
} }
@ -47,8 +50,7 @@ async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
return user, password return user, password
async def upgrade_old_refresh_token(connection: AsyncConnection, user,refresh_token) -> Optional[User]: async def upgrade_old_refresh_token(connection: AsyncConnection, user, refresh_token) -> Optional[User]:
new_status = KeyStatus.EXPIRED new_status = KeyStatus.EXPIRED
update_query = ( update_query = (
@ -57,7 +59,7 @@ async def upgrade_old_refresh_token(connection: AsyncConnection, user,refresh_to
account_table.c.id == user.id, account_table.c.id == user.id,
account_keyring_table.c.status == KeyStatus.ACTIVE, account_keyring_table.c.status == KeyStatus.ACTIVE,
account_keyring_table.c.key_type == KeyType.REFRESH_TOKEN, account_keyring_table.c.key_type == KeyType.REFRESH_TOKEN,
account_keyring_table.c.key_value == refresh_token account_keyring_table.c.key_value == refresh_token,
) )
.values(status=new_status) .values(status=new_status)
) )
@ -67,8 +69,9 @@ async def upgrade_old_refresh_token(connection: AsyncConnection, user,refresh_to
await connection.commit() await connection.commit()
async def add_new_refresh_token(connection: AsyncConnection, new_refresh_token, new_refresh_token_expires_time, user) -> Optional[User]: async def add_new_refresh_token(
connection: AsyncConnection, new_refresh_token, new_refresh_token_expires_time, user
) -> Optional[User]:
new_refresh_token = account_keyring_table.insert().values( new_refresh_token = account_keyring_table.insert().values(
owner_id=user.id, owner_id=user.id,
key_type=KeyType.REFRESH_TOKEN, key_type=KeyType.REFRESH_TOKEN,

View File

@ -5,7 +5,7 @@ from enum import Enum
from sqlalchemy import insert, select from sqlalchemy import insert, select
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.tables.account import account_keyring_table from api.db.tables.account import account_keyring_table
from api.schemas.account.account_keyring import AccountKeyring from api.schemas.account.account_keyring import AccountKeyring
@ -16,10 +16,7 @@ async def get_key_by_id(connection: AsyncConnection, key_id: str) -> Optional[Ac
""" """
Получает key по key_id. Получает key по key_id.
""" """
query = ( query = select(account_keyring_table).where(account_keyring_table.c.key_id == key_id)
select(account_keyring_table)
.where(account_keyring_table.c.key_id == key_id)
)
user_db_cursor = await connection.execute(query) user_db_cursor = await connection.execute(query)
user_db = user_db_cursor.one_or_none() user_db = user_db_cursor.one_or_none()
@ -28,8 +25,11 @@ async def get_key_by_id(connection: AsyncConnection, key_id: str) -> Optional[Ac
return None return None
user_data = { user_data = {
column.name: (getattr(user_db, column.name).name if isinstance( column.name: (
getattr(user_db, column.name), Enum) else getattr(user_db, column.name)) getattr(user_db, column.name).name
if isinstance(getattr(user_db, column.name), Enum)
else getattr(user_db, column.name)
)
for column in account_keyring_table.columns for column in account_keyring_table.columns
} }
@ -41,30 +41,28 @@ async def update_key_by_id(connection: AsyncConnection, update_values, key) -> O
Вносит изменеия в нужное поле таблицы account_keyring_table. Вносит изменеия в нужное поле таблицы account_keyring_table.
""" """
await connection.execute( await connection.execute(
account_keyring_table.update() account_keyring_table.update().where(account_keyring_table.c.key_id == key.key_id).values(**update_values)
.where(account_keyring_table.c.key_id == key.key_id) )
.values(**update_values)
)
await connection.commit() await connection.commit()
async def create_key(connection: AsyncConnection, key: AccountKeyring, key_id:int) -> Optional[AccountKeyring]: async def create_key(connection: AsyncConnection, key: AccountKeyring, key_id: int) -> Optional[AccountKeyring]:
""" """
Создает нове поле в таблице account_keyring_table). Создает нове поле в таблице account_keyring_table).
""" """
query = insert(account_keyring_table).values( query = insert(account_keyring_table).values(
owner_id = key.owner_id, owner_id=key.owner_id,
key_type= key.key_type.value, key_type=key.key_type.value,
key_id= key_id, key_id=key_id,
key_value= key.key_value, key_value=key.key_value,
created_at= datetime.now(timezone.utc), created_at=datetime.now(timezone.utc),
expiry= key.expiry, expiry=key.expiry,
status= key.status.value status=key.status.value,
) )
key.created_at= datetime.now(timezone.utc) key.created_at = datetime.now(timezone.utc)
key.key_id= key_id key.key_id = key_id
await connection.execute(query) await connection.execute(query)

View File

@ -1 +1 @@
from . import account,events,process from . import account, events, process

View File

@ -21,22 +21,23 @@ class AccountStatus(str,Enum):
account_table = Table( account_table = Table(
'account', metadata, "account",
Column('id', UnsignedInt, primary_key=True, autoincrement=True), metadata,
Column('name', String(100), nullable=False), Column("id", UnsignedInt, primary_key=True, autoincrement=True),
Column('login', String(100), nullable=False), Column("name", String(100), nullable=False),
Column('email', String(100), nullable=True), Column("login", String(100), nullable=False),
Column('bind_tenant_id', String(40), nullable=True), Column("email", String(100), nullable=True),
Column('role', SQLAEnum(AccountRole), nullable=False), Column("bind_tenant_id", String(40), nullable=True),
Column('meta', JSON, default={}), Column("role", SQLAEnum(AccountRole), nullable=False),
Column('creator_id', UnsignedInt, ForeignKey('account.id'), nullable=True), Column("meta", JSON, default={}),
Column('created_at', DateTime(timezone=True), server_default=func.now()), Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=True),
Column('status', SQLAEnum(AccountStatus), nullable=False), Column("created_at", DateTime(timezone=True), server_default=func.now()),
Column("status", SQLAEnum(AccountStatus), nullable=False),
Index('idx_login', 'login'), Index("idx_login", "login"),
Index('idx_name', 'name'), Index("idx_name", "name"),
) )
class KeyType(str,Enum): class KeyType(str,Enum):
PASSWORD = "PASSWORD" PASSWORD = "PASSWORD"
ACCESS_TOKEN = "ACCESS_TOKEN" ACCESS_TOKEN = "ACCESS_TOKEN"
@ -48,12 +49,15 @@ class KeyStatus(str,Enum):
EXPIRED = "EXPIRED" EXPIRED = "EXPIRED"
DELETED = "DELETED" DELETED = "DELETED"
account_keyring_table = Table( account_keyring_table = Table(
'account_keyring', metadata, "account_keyring",
Column('owner_id', UnsignedInt, ForeignKey('account.id'), primary_key=True, nullable=False), metadata,
Column('key_type', SQLAEnum(KeyType), primary_key=True, nullable=False), Column("owner_id", UnsignedInt, ForeignKey("account.id"), primary_key=True, nullable=False),
Column('key_id', String(40),primary_key=True, default=None), Column("key_type", SQLAEnum(KeyType), primary_key=True, nullable=False),
Column('key_value', String(255), nullable=False), Column("key_id", String(40), primary_key=True, default=None),
Column('created_at', DateTime(timezone=True), server_default=func.now()), Column("key_value", String(255), nullable=False),
Column('expiry', DateTime(timezone=True), nullable=True), Column("created_at", DateTime(timezone=True), server_default=func.now()),
Column('status', SQLAEnum(KeyStatus), nullable=False), ) Column("expiry", DateTime(timezone=True), nullable=True),
Column("status", SQLAEnum(KeyStatus), nullable=False),
)

View File

@ -6,10 +6,12 @@ from api.db.sql_types import UnsignedInt
from api.db import metadata from api.db import metadata
class EventState(str, Enum): class EventState(str, Enum):
AUTO = auto() AUTO = auto()
DESCRIPTED = auto() DESCRIPTED = auto()
class EventStatus(str, Enum): class EventStatus(str, Enum):
ACTIVE = auto() ACTIVE = auto()
DISABLED = auto() DISABLED = auto()
@ -17,13 +19,14 @@ class EventStatus(str, Enum):
list_events_table = Table( list_events_table = Table(
'list_events', metadata, "list_events",
Column('id', UnsignedInt, primary_key=True, autoincrement=True), metadata,
Column('name', String(40, collation='latin1_bin'), nullable=False,unique=True), Column("id", UnsignedInt, primary_key=True, autoincrement=True),
Column('title', String(64), nullable=False), Column("name", String(40, collation="latin1_bin"), nullable=False, unique=True),
Column('creator_id', UnsignedInt, ForeignKey('account.id'), nullable=False), Column("title", String(64), nullable=False),
Column('created_at', DateTime(timezone=True), server_default=func.now()), Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
Column('schema', JSON, default={}), Column("created_at", DateTime(timezone=True), server_default=func.now()),
Column('state', SQLAEnum(EventState), nullable=False), Column("schema", JSON, default={}),
Column('status', SQLAEnum(EventStatus), nullable=False), Column("state", SQLAEnum(EventState), nullable=False),
Column("status", SQLAEnum(EventStatus), nullable=False),
) )

View File

@ -1,4 +1,16 @@
from sqlalchemy import Table, Column, Integer, String, Text, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index, PrimaryKeyConstraint from sqlalchemy import (
Table,
Column,
Integer,
String,
Text,
Enum as SQLAEnum,
JSON,
ForeignKey,
DateTime,
Index,
PrimaryKeyConstraint,
)
from sqlalchemy.sql import func from sqlalchemy.sql import func
from enum import Enum, auto from enum import Enum, auto
@ -7,7 +19,6 @@ from api.db.sql_types import UnsignedInt
from api.db import metadata from api.db import metadata
class ProcessStatus(str, Enum): class ProcessStatus(str, Enum):
ACTIVE = auto() ACTIVE = auto()
STOPPING = auto() STOPPING = auto()
@ -16,69 +27,80 @@ class ProcessStatus(str, Enum):
process_schema_table = Table( process_schema_table = Table(
'process_schema', metadata, "process_schema",
Column('id', UnsignedInt, primary_key=True, autoincrement=True), metadata,
Column('title', String(100), nullable=False), Column("id", UnsignedInt, primary_key=True, autoincrement=True),
Column('description', Text, nullable=False), Column("title", String(100), nullable=False),
Column('owner_id', UnsignedInt, ForeignKey('account.id'), nullable=False), Column("description", Text, nullable=False),
Column('creator_id', UnsignedInt, ForeignKey('account.id'), nullable=False), Column("owner_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
Column('created_at', DateTime(timezone=True), server_default=func.now()), Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
Column('settings', JSON, default={}), Column("created_at", DateTime(timezone=True), server_default=func.now()),
Column('status', SQLAEnum(ProcessStatus), nullable=False), Column("settings", JSON, default={}),
Column("status", SQLAEnum(ProcessStatus), nullable=False),
Index('idx_owner_id', 'owner_id',) Index(
"idx_owner_id",
"owner_id",
),
) )
process_version_archive_table = Table( process_version_archive_table = Table(
'process_version_archive', metadata, "process_version_archive",
Column('id', UnsignedInt, autoincrement=True, nullable=False), metadata,
Column('ps_id', UnsignedInt, ForeignKey('process_schema.id'), nullable=False), Column("id", UnsignedInt, autoincrement=True, nullable=False),
Column('version', UnsignedInt, default=1, nullable=False), Column("ps_id", UnsignedInt, ForeignKey("process_schema.id"), nullable=False),
Column('snapshot', JSON, default={}), Column("version", UnsignedInt, default=1, nullable=False),
Column('owner_id', UnsignedInt, ForeignKey('account.id'), nullable=False), Column("snapshot", JSON, default={}),
Column('created_at', DateTime(timezone=True), server_default=func.now()), Column("owner_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
Column('is_last', UnsignedInt, default=0), Column("created_at", DateTime(timezone=True), server_default=func.now()),
PrimaryKeyConstraint('id', 'version') ) Column("is_last", UnsignedInt, default=0),
PrimaryKeyConstraint("id", "version"),
)
class NodeStatus(str, Enum): class NodeStatus(str, Enum):
ACTIVE = auto() ACTIVE = auto()
DISABLED = auto() DISABLED = auto()
DELETED = auto() DELETED = auto()
class NodeType(Enum): class NodeType(Enum):
TYPE1 = 'Type1' TYPE1 = "Type1"
TYPE2 = 'Type2' TYPE2 = "Type2"
TYPE3 = 'Type3' TYPE3 = "Type3"
ps_node_table = Table( ps_node_table = Table(
'ps_node', metadata, "ps_node",
Column('id', UnsignedInt, autoincrement=True, primary_key=True, nullable=False), metadata,
Column('ps_id', UnsignedInt, ForeignKey('process_schema.id'), nullable=False), Column("id", UnsignedInt, autoincrement=True, primary_key=True, nullable=False),
Column('node_type', SQLAEnum(NodeType), nullable=False), Column("ps_id", UnsignedInt, ForeignKey("process_schema.id"), nullable=False),
Column('settings', JSON, default={}), Column("node_type", SQLAEnum(NodeType), nullable=False),
Column('creator_id', UnsignedInt, ForeignKey('account.id'), nullable=False), Column("settings", JSON, default={}),
Column('created_at', DateTime(timezone=True), server_default=func.now()), Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
Column('status', SQLAEnum(NodeStatus), nullable=False), Column("created_at", DateTime(timezone=True), server_default=func.now()),
Column("status", SQLAEnum(NodeStatus), nullable=False),
Index('idx_ps_id', 'ps_id') Index("idx_ps_id", "ps_id"),
) )
class NodeLinkStatus(str, Enum): class NodeLinkStatus(str, Enum):
ACTIVE = auto() ACTIVE = auto()
STOPPING = auto() STOPPING = auto()
STOPPED = auto() STOPPED = auto()
DELETED = auto() DELETED = auto()
node_link_table = Table(
'node_link', metadata,
Column('id', UnsignedInt, autoincrement=True, primary_key=True, nullable=False),
Column('link_name', String(20), nullable=False),
Column('node_id', UnsignedInt, ForeignKey('ps_node.id'), nullable=False),
Column('next_node_id', UnsignedInt, ForeignKey('ps_node.id'), nullable=False),
Column('settings', JSON, default={}),
Column('creator_id', UnsignedInt, ForeignKey('account.id'), nullable=False),
Column('created_at', DateTime(timezone=True), server_default=func.now()),
Column('status', SQLAEnum(NodeLinkStatus),nullable=False),
Index('idx_node_id', 'node_id'), node_link_table = Table(
Index('idx_next_node_id', 'next_node_id')) "node_link",
metadata,
Column("id", UnsignedInt, autoincrement=True, primary_key=True, nullable=False),
Column("link_name", String(20), nullable=False),
Column("node_id", UnsignedInt, ForeignKey("ps_node.id"), nullable=False),
Column("next_node_id", UnsignedInt, ForeignKey("ps_node.id"), nullable=False),
Column("settings", JSON, default={}),
Column("creator_id", UnsignedInt, ForeignKey("account.id"), nullable=False),
Column("created_at", DateTime(timezone=True), server_default=func.now()),
Column("status", SQLAEnum(NodeLinkStatus), nullable=False),
Index("idx_node_id", "node_id"),
Index("idx_next_node_id", "next_node_id"),
)

View File

@ -2,11 +2,8 @@ from api.endpoints.auth import api_router as auth_router
from api.endpoints.profile import api_router as profile_router from api.endpoints.profile import api_router as profile_router
from api.endpoints.account import api_router as account_router from api.endpoints.account import api_router as account_router
from api.endpoints.keyring import api_router as keyring_router from api.endpoints.keyring import api_router as keyring_router
list_of_routes = [
auth_router, list_of_routes = [auth_router, profile_router, account_router, keyring_router]
profile_router,
account_router,
keyring_router]
__all__ = [ __all__ = [
"list_of_routes", "list_of_routes",

View File

@ -28,7 +28,6 @@ api_router = APIRouter(
tags=["User accountModel"], tags=["User accountModel"],
) )
@api_router.get("",response_model=AllUserResponse) @api_router.get("",response_model=AllUserResponse)
async def get_all_account( async def get_all_account(
@ -87,33 +86,23 @@ async def create_account(
else: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST, detail="An account with this information already exists."
detail="An account with this information already exists.") )
@api_router.put("/{user_id}", response_model=User) @api_router.put("/{user_id}", response_model=User)
async def update_account( async def update_account(
user_id: int, user_id: int, request: Request, user_update: UserUpdate, connection: AsyncConnection = Depends(get_connection_dep)
request: Request, ):
user_update: UserUpdate,
connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user) authorize_user = await db_user_role_validation(connection, current_user)
user = await get_user_by_id(connection, user_id) user = await get_user_by_id(connection, user_id)
if user is None: if user is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="Account not found")
update_values = update_user_data_changes(user_update, user)
update_values = update_user_data_changes(user_update,user)
if update_values is None: if update_values is None:
return user return user
@ -139,17 +128,13 @@ async def delete_account(
authorize_user = await db_user_role_validation(connection, current_user) authorize_user = await db_user_role_validation(connection, current_user)
user = await get_user_by_id(connection, user_id) user = await get_user_by_id(connection, user_id)
if user is None: if user is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="Account not found")
user_update = UserUpdate(status=AccountStatus.DELETED.value) user_update = UserUpdate(status=AccountStatus.DELETED.value)
update_values = update_user_data_changes(user_update,user) update_values = update_user_data_changes(user_update, user)
if update_values is None: if update_values is None:
return user return user

View File

@ -9,9 +9,7 @@ from fastapi import (
status, status,
) )
from loguru import logger from loguru import logger
from pydantic.main import BaseModel
from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth import AuthJWT
from pydantic import BaseModel from pydantic import BaseModel
@ -22,7 +20,7 @@ from api.config import get_settings
from api.db.connection.session import get_connection_dep from api.db.connection.session import get_connection_dep
from api.services.auth import authenticate_user from api.services.auth import authenticate_user
from api.db.logic.auth import add_new_refresh_token,upgrade_old_refresh_token from api.db.logic.auth import add_new_refresh_token, upgrade_old_refresh_token
from api.schemas.endpoints.auth import Auth, Access from api.schemas.endpoints.auth import Auth, Access
@ -52,12 +50,11 @@ def get_config():
@api_router.post("", response_model=Access) @api_router.post("", response_model=Access)
async def login_for_access_token( async def login_for_access_token(
user: Auth, user: Auth,
response: Response, response: Response,
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
Authorize: AuthJWT = Depends(), Authorize: AuthJWT = Depends(),
): ):
"""Авторизирует, выставляет токены в куки.""" """Авторизирует, выставляет токены в куки."""
user = await authenticate_user(connection, user.login, user.password) user = await authenticate_user(connection, user.login, user.password)
@ -71,25 +68,18 @@ async def login_for_access_token(
# headers={"WWW-Authenticate": "Bearer"}, # headers={"WWW-Authenticate": "Bearer"},
) )
access_token_expires = timedelta( access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
refresh_token_expires = timedelta( refresh_token_expires = timedelta(days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS)
days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS
)
logger.debug(f"refresh_token_expires {refresh_token_expires}") logger.debug(f"refresh_token_expires {refresh_token_expires}")
access_token = Authorize.create_access_token( access_token = Authorize.create_access_token(subject=user.login, expires_time=access_token_expires)
subject=user.login, expires_time=access_token_expires refresh_token = Authorize.create_refresh_token(subject=user.login, expires_time=refresh_token_expires)
)
refresh_token = Authorize.create_refresh_token(
subject=user.login, expires_time=refresh_token_expires
)
refresh_token_expires_time = datetime.now(timezone.utc) + refresh_token_expires refresh_token_expires_time = datetime.now(timezone.utc) + refresh_token_expires
await add_new_refresh_token(connection,refresh_token,refresh_token_expires_time,user) await add_new_refresh_token(connection, refresh_token, refresh_token_expires_time, user)
Authorize.set_refresh_cookies(refresh_token) Authorize.set_refresh_cookies(refresh_token)
@ -99,11 +89,8 @@ async def login_for_access_token(
@api_router.post("/refresh",response_model=Access) @api_router.post("/refresh",response_model=Access)
async def refresh( async def refresh(
request: Request, request: Request, connection: AsyncConnection = Depends(get_connection_dep), Authorize: AuthJWT = Depends()
connection: AsyncConnection = Depends(get_connection_dep), ):
Authorize: AuthJWT = Depends()
):
refresh_token = request.cookies.get("refresh_token_cookie") refresh_token = request.cookies.get("refresh_token_cookie")
# print("Refresh Token:", refresh_token) # print("Refresh Token:", refresh_token)
@ -111,24 +98,19 @@ async def refresh(
raise HTTPException(status_code=401, detail="Refresh token is missing") raise HTTPException(status_code=401, detail="Refresh token is missing")
try: try:
Authorize.jwt_refresh_token_required() Authorize.jwt_refresh_token_required()
current_user = Authorize.get_jwt_subject() current_user = Authorize.get_jwt_subject()
except Exception as e: except Exception as e:
await upgrade_old_refresh_token(connection, current_user, refresh_token)
await upgrade_old_refresh_token(connection,current_user,refresh_token)
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token", detail="Invalid refresh token",
) )
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES) access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
new_access_token = Authorize.create_access_token( new_access_token = Authorize.create_access_token(subject=current_user, expires_time=access_token_expires)
subject=current_user, expires_time=access_token_expires
)
return Access(access_token=new_access_token) return Access(access_token=new_access_token)

View File

@ -47,22 +47,19 @@ async def get_keyring(
keyring = await get_key_by_id(connection, key_id) keyring = await get_key_by_id(connection, key_id)
if keyring is None: if keyring is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Key not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="Key not found")
return keyring return keyring
@api_router.post("/{user_id}/{key_id}", response_model=AccountKeyring) @api_router.post("/{user_id}/{key_id}", response_model=AccountKeyring)
async def create_keyring( async def create_keyring(
user_id: int, user_id: int,
key_id: str, key_id: str,
request: Request, request: Request,
key: AccountKeyringUpdate, key: AccountKeyringUpdate,
connection: AsyncConnection = Depends(get_connection_dep) connection: AsyncConnection = Depends(get_connection_dep),
): ):
current_user = request.state.current_user current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user) authorize_user = await db_user_role_validation(connection, current_user)
@ -75,40 +72,33 @@ async def create_keyring(
else: else:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, status_code=status.HTTP_400_BAD_REQUEST, detail="An keyring with this information already exists."
detail="An keyring with this information already exists.") )
@api_router.put("/{user_id}/{key_id}", response_model=AccountKeyring) @api_router.put("/{user_id}/{key_id}", response_model=AccountKeyring)
async def update_keyring( async def update_keyring(
user_id: int, user_id: int,
key_id: str, key_id: str,
request: Request, request: Request,
keyring_update: AccountKeyringUpdate, keyring_update: AccountKeyringUpdate,
connection: AsyncConnection = Depends(get_connection_dep) connection: AsyncConnection = Depends(get_connection_dep),
): ):
current_user = request.state.current_user current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user) authorize_user = await db_user_role_validation(connection, current_user)
keyring = await get_key_by_id(connection, key_id) keyring = await get_key_by_id(connection, key_id)
if keyring is None: if keyring is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="keyring not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="keyring not found")
update_values = update_key_data_changes(keyring_update, keyring)
update_values = update_key_data_changes(keyring_update,keyring)
if update_values is None: if update_values is None:
return keyring return keyring
keyring_update_data = AccountKeyring.model_validate({**keyring.model_dump(), **update_values}) keyring_update_data = AccountKeyring.model_validate({**keyring.model_dump(), **update_values})
await update_key_by_id(connection, update_values, keyring) await update_key_by_id(connection, update_values, keyring)
@ -118,27 +108,19 @@ async def update_keyring(
@api_router.delete("/{user_id}/{key_id}", response_model=AccountKeyring) @api_router.delete("/{user_id}/{key_id}", response_model=AccountKeyring)
async def delete_keyring( async def delete_keyring(
user_id: int, user_id: int, key_id: str, request: Request, connection: AsyncConnection = Depends(get_connection_dep)
key_id: str, ):
request: Request,
connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user) authorize_user = await db_user_role_validation(connection, current_user)
keyring = await get_key_by_id(connection, key_id) keyring = await get_key_by_id(connection, key_id)
if keyring is None: if keyring is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="keyring not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="keyring not found")
keyring_update = AccountKeyringUpdate(status=KeyStatus.DELETED.value) keyring_update = AccountKeyringUpdate(status=KeyStatus.DELETED.value)
update_values = update_key_data_changes(keyring_update,keyring) update_values = update_key_data_changes(keyring_update, keyring)
if update_values is None: if update_values is None:
return keyring return keyring

View File

@ -28,50 +28,42 @@ api_router = APIRouter(
@api_router.get("",response_model=User) @api_router.get("",response_model=User)
async def get_profile( async def get_profile(
request: Request, request: Request,
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
): ):
# Извлекаем текущего пользователя из request.state # Извлекаем текущего пользователя из request.state
current_user = request.state.current_user current_user = request.state.current_user
user = await get_user_by_login(connection, current_user) user = await get_user_by_login(connection, current_user)
if user is None: if user is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="Account not found")
return user return user
@api_router.put("",response_model=User) @api_router.put("",response_model=User)
async def update_profile( async def update_profile(
request: Request, request: Request,
user_updata: UserUpdate, user_updata: UserUpdate,
connection: AsyncConnection = Depends(get_connection_dep), connection: AsyncConnection = Depends(get_connection_dep),
):
):
current_user = request.state.current_user current_user = request.state.current_user
user = await get_user_by_login(connection, current_user) user = await get_user_by_login(connection, current_user)
if user is None: if user is None:
raise HTTPException( raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
status_code=status.HTTP_404_NOT_FOUND,
detail="Account not found")
if user_updata.role == None and user_updata.login == None: if user_updata.role == None and user_updata.login == None:
update_values = update_user_data_changes(user_updata,user) update_values = update_user_data_changes(user_updata, user)
if update_values is None: if update_values is None:
return user return user
await update_user_by_id(connection, update_values, user) await update_user_by_id(connection, update_values, user)
user = await get_user_by_id(connection, user.id) user = await get_user_by_id(connection, user.id)
return user return user
else: else:
raise HTTPException( raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Bad body")
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY ,
detail="Bad body")

View File

@ -5,7 +5,7 @@ from pydantic import BaseModel, EmailStr, Field
from api.db.tables.account import AccountRole,AccountStatus from api.db.tables.account import AccountRole,AccountStatus
class User(BaseModel): class User(Base):
id: Optional[int] = None id: Optional[int] = None
name: str = Field(..., max_length=100) name: str = Field(..., max_length=100)
login: str = Field(..., max_length=100) login: str = Field(..., max_length=100)

View File

@ -5,7 +5,7 @@ from datetime import datetime
from api.db.tables.account import KeyType,KeyStatus from api.db.tables.account import KeyType,KeyStatus
class AccountKeyring(BaseModel): class AccountKeyring(Base):
owner_id: int owner_id: int
key_type: KeyType key_type: KeyType
key_id: Optional[str] = Field(None, max_length=40) key_id: Optional[str] = Field(None, max_length=40)

10
api/api/schemas/base.py Normal file
View File

@ -0,0 +1,10 @@
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
class Base(BaseModel):
model_config = ConfigDict(
from_attributes=True,
alias_generator=to_camel,
populate_by_name=True,
)

View File

@ -6,7 +6,7 @@ from api.db.tables.account import AccountRole,AccountStatus
class UserUpdate(BaseModel): class UserUpdate(Base):
id: Optional[int] = None id: Optional[int] = None
name: Optional[str] = Field(None, max_length=100) name: Optional[str] = Field(None, max_length=100)
login: Optional[str] = Field(None, max_length=100) login: Optional[str] = Field(None, max_length=100)

View File

@ -1,11 +1,11 @@
import datetime import datetime
from typing import Optional from typing import Optional
from pydantic import BaseModel, Field from pydantic import Field
from datetime import datetime from datetime import datetime
from api.db.tables.account import KeyType,KeyStatus from api.db.tables.account import KeyType,KeyStatus
class AccountKeyringUpdate(BaseModel): class AccountKeyringUpdate(Base):
owner_id: Optional[int] = None owner_id: Optional[int] = None
key_type: Optional[KeyType] = None key_type: Optional[KeyType] = None
key_id: Optional[str] = Field(None, max_length=40) key_id: Optional[str] = Field(None, max_length=40)

View File

@ -1,13 +1,19 @@
from pydantic import BaseModel from api.schemas.base import Base
# Таблица для получения информации из запроса # Таблица для получения информации из запроса
class Auth(BaseModel):
class Auth(Base):
login: str login: str
password: str password: str
class Refresh(BaseModel): class AccessToken(Base):
access_token: str
class Refresh(Base):
refresh_token: str refresh_token: str
class Access(BaseModel): class Access(BaseModel):

View File

@ -1,8 +1,10 @@
from pydantic import BaseModel, Field from pydantic import Field
from typing import Dict, Any from typing import Dict, Any
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from api.schemas.base import Base
class State(Enum): class State(Enum):
AUTO = "Auto" AUTO = "Auto"
@ -14,7 +16,8 @@ class Status(Enum):
DISABLED = "Disabled" DISABLED = "Disabled"
DELETED = "Deleted" DELETED = "Deleted"
class ListEvent(BaseModel):
class ListEvent(Base):
id: int id: int
name: str = Field(..., max_length=40) name: str = Field(..., max_length=40)
title: str = Field(..., max_length=64) title: str = Field(..., max_length=64)

View File

@ -1,15 +1,19 @@
from pydantic import BaseModel, Field, conint from pydantic import Field, conint
from typing import Dict, Any from typing import Dict, Any
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from api.schemas.base import Base
class Status(Enum): class Status(Enum):
ACTIVE = "Active" ACTIVE = "Active"
STOPPING = "Stopping" STOPPING = "Stopping"
STOPPED = "Stopped" STOPPED = "Stopped"
DELETED = "Deleted" DELETED = "Deleted"
class MyModel(BaseModel):
class MyModel(Base):
id: int id: int
link_name: str = Field(..., max_length=20) link_name: str = Field(..., max_length=20)
node_id: int node_id: int

View File

@ -1,15 +1,19 @@
from pydantic import BaseModel, Field from pydantic import Field
from typing import Dict, Any from typing import Dict, Any
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from api.schemas.base import Base
class Status(Enum): class Status(Enum):
ACTIVE = "Active" ACTIVE = "Active"
STOPPING = "Stopping" STOPPING = "Stopping"
STOPPED = "Stopped" STOPPED = "Stopped"
DELETED = "Deleted" DELETED = "Deleted"
class ProcessSchema(BaseModel):
class ProcessSchema(Base):
id: int id: int
title: str = Field(..., max_length=100) title: str = Field(..., max_length=100)
description: str description: str

View File

@ -1,8 +1,10 @@
from pydantic import BaseModel, Field
from typing import Dict, Any from typing import Dict, Any
from datetime import datetime from datetime import datetime
class ProcessStatusSchema(BaseModel): from api.schemas.base import Base
class ProcessStatusSchema(Base):
id: int id: int
version: int version: int
snapshot: Dict[str, Any] snapshot: Dict[str, Any]

View File

@ -1,19 +1,21 @@
from pydantic import BaseModel
from datetime import datetime from datetime import datetime
from typing import Dict, Any from typing import Dict, Any
from enum import Enum from enum import Enum
from api.schemas.base import Base
class NodeType(Enum): class NodeType(Enum):
pass pass
class Status(Enum): class Status(Enum):
ACTIVE = "Active" ACTIVE = "Active"
DISABLED = "Disabled" DISABLED = "Disabled"
DELETED = "Deleted" DELETED = "Deleted"
class Ps_Node(BaseModel):
class Ps_Node(Base):
id: int id: int
ps_id: int ps_id: int
node_type: NodeType node_type: NodeType

View File

@ -1,6 +1,7 @@
from typing import Optional from typing import Optional
from sqlalchemy.ext.asyncio import AsyncConnection from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.logic.auth import get_user from api.db.logic.auth import get_user
# # from backend.schemas.users.token import TokenData # # from backend.schemas.users.token import TokenData
from api.schemas.account.account import User from api.schemas.account.account import User
from api.db.tables.account import AccountStatus from api.db.tables.account import AccountStatus
@ -8,13 +9,8 @@ from api.db.tables.account import AccountStatus
from api.utils.hasher import Hasher from api.utils.hasher import Hasher
async def authenticate_user(connection: AsyncConnection, username: str, password: str) -> Optional[User]:
sql_user, sql_password = await get_user(connection, username)
async def authenticate_user(
connection: AsyncConnection, username: str, password: str
) -> Optional[User]:
sql_user,sql_password = await get_user(connection, username)
if not sql_user or sql_user.status != AccountStatus.ACTIVE : if not sql_user or sql_user.status != AccountStatus.ACTIVE :
return None return None

View File

@ -14,52 +14,43 @@ from re import escape
from fastapi_jwt_auth import AuthJWT from fastapi_jwt_auth import AuthJWT
class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware): class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware):
def __init__(self, app): def __init__(self, app):
super().__init__(app) super().__init__(app)
self.prefix = escape(get_settings().PATH_PREFIX) self.prefix = escape(get_settings().PATH_PREFIX)
self.excluded_routes = [ self.excluded_routes = [
re.compile(r'^' + re.escape(self.prefix) + r'/auth/refresh/?$'), re.compile(r"^" + re.escape(self.prefix) + r"/auth/refresh/?$"),
re.compile(r'^' + re.escape(self.prefix) + r'/auth/?$') re.compile(r"^" + re.escape(self.prefix) + r"/auth/?$"),
] ]
async def dispatch(self, request: Request, call_next):
async def dispatch(self,
request: Request,
call_next):
if request.method in ["GET", "POST", "PUT", "DELETE"]: if request.method in ["GET", "POST", "PUT", "DELETE"]:
if any(pattern.match(request.url.path) for pattern in self.excluded_routes): if any(pattern.match(request.url.path) for pattern in self.excluded_routes):
return await call_next(request) return await call_next(request)
else: else:
auth_header = request.headers.get("Authorization") auth_header = request.headers.get("Authorization")
if not auth_header: if not auth_header:
return JSONResponse( return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "Missing authorization header."}, content={"detail": "Missing authorization header."},
headers={"WWW-Authenticate": "Bearer"} headers={"WWW-Authenticate": "Bearer"},
) )
token = auth_header.split(" ")[1] token = auth_header.split(" ")[1]
Authorize = AuthJWT(request) Authorize = AuthJWT(request)
try: try:
current_user = Authorize.get_jwt_subject() current_user = Authorize.get_jwt_subject()
request.state.current_user = current_user request.state.current_user = current_user
return await call_next(request) return await call_next(request)
except Exception: except Exception:
return JSONResponse( return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "The access token is invalid or expired."}, content={"detail": "The access token is invalid or expired."},
headers={"WWW-Authenticate": "Bearer"} headers={"WWW-Authenticate": "Bearer"},
) )
# async with get_connection() as connection: # async with get_connection() as connection:
# authorize_user = await get_user_login(connection, current_user) # authorize_user = await get_user_login(connection, current_user)

View File

@ -5,6 +5,7 @@ from api.db.tables.account import KeyType,KeyStatus
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.db.tables.account import AccountRole,AccountStatus from api.db.tables.account import AccountRole,AccountStatus
def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]: def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]:
""" """
Сравнивает данные для обновления с текущими значениями пользователя. Сравнивает данные для обновления с текущими значениями пользователя.
@ -38,6 +39,7 @@ def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]:
return changes if changes else None return changes if changes else None
def update_key_data_changes(update_data: AccountKeyringUpdate, key) -> Optional[dict]: def update_key_data_changes(update_data: AccountKeyringUpdate, key) -> Optional[dict]:
""" """
Сравнивает данные для обновления с текущими значениями пользователя. Сравнивает данные для обновления с текущими значениями пользователя.

View File

@ -2,6 +2,7 @@ import hashlib
# Хешер для работы с паролем. # Хешер для работы с паролем.
class Hasher: class Hasher:
def __init__(self): def __init__(self):
pass pass

View File

@ -3,6 +3,7 @@ from datetime import datetime
# Генератор key_id для таблицы account_keyring # Генератор key_id для таблицы account_keyring
def KeyIdGenerator(): def KeyIdGenerator():
random_number = random.randint(1000, 9999) random_number = random.randint(1000, 9999)
result = f"{datetime.now().strftime('%Y-%m-%d')}-{random_number}" result = f"{datetime.now().strftime('%Y-%m-%d')}-{random_number}"

595
api/poetry.lock generated
View File

@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. # This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]] [[package]]
name = "aio-pika" name = "aio-pika"
@ -106,6 +106,18 @@ doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)",
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
trio = ["trio (>=0.26.1)"] trio = ["trio (>=0.26.1)"]
[[package]]
name = "certifi"
version = "2025.4.26"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
groups = ["main"]
files = [
{file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"},
{file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"},
]
[[package]] [[package]]
name = "cffi" name = "cffi"
version = "1.17.1" version = "1.17.1"
@ -338,14 +350,40 @@ files = [
] ]
[package.dependencies] [package.dependencies]
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"standard\""}
fastapi-cli = {version = ">=0.0.5", extras = ["standard"], optional = true, markers = "extra == \"standard\""}
httpx = {version = ">=0.23.0", optional = true, markers = "extra == \"standard\""}
jinja2 = {version = ">=3.1.5", optional = true, markers = "extra == \"standard\""}
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
python-multipart = {version = ">=0.0.18", optional = true, markers = "extra == \"standard\""}
starlette = ">=0.40.0,<0.47.0" starlette = ">=0.40.0,<0.47.0"
typing-extensions = ">=4.8.0" typing-extensions = ">=4.8.0"
uvicorn = {version = ">=0.12.0", extras = ["standard"], optional = true, markers = "extra == \"standard\""}
[package.extras] [package.extras]
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "fastapi-cli"
version = "0.0.7"
description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4"},
{file = "fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e"},
]
[package.dependencies]
rich-toolkit = ">=0.11.1"
typer = ">=0.12.3"
uvicorn = {version = ">=0.15.0", extras = ["standard"]}
[package.extras]
standard = ["uvicorn[standard] (>=0.15.0)"]
[[package]] [[package]]
name = "fastapi-jwt-auth" name = "fastapi-jwt-auth"
version = "0.5.0" version = "0.5.0"
@ -472,6 +510,109 @@ files = [
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
] ]
[[package]]
name = "httpcore"
version = "1.0.8"
description = "A minimal low-level HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"},
{file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"},
]
[package.dependencies]
certifi = "*"
h11 = ">=0.13,<0.15"
[package.extras]
asyncio = ["anyio (>=4.0,<5.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httptools"
version = "0.6.4"
description = "A collection of framework independent HTTP protocol utils."
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
files = [
{file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"},
{file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"},
{file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"},
{file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"},
{file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"},
{file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"},
{file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"},
{file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"},
{file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"},
{file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"},
{file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"},
{file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"},
{file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"},
{file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"},
{file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"},
{file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"},
{file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"},
{file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"},
{file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"},
{file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"},
{file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"},
{file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"},
{file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"},
{file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"},
{file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"},
{file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"},
{file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"},
{file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"},
{file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"},
{file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"},
{file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"},
{file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"},
{file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"},
{file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"},
{file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"},
{file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"},
{file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"},
{file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"},
{file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"},
{file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"},
{file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"},
{file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"},
{file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"},
]
[package.extras]
test = ["Cython (>=0.29.24)"]
[[package]]
name = "httpx"
version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
[package.extras]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.10" version = "3.10"
@ -487,6 +628,24 @@ files = [
[package.extras] [package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
[[package]]
name = "jinja2"
version = "3.1.6"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
]
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]] [[package]]
name = "loguru" name = "loguru"
version = "0.7.3" version = "0.7.3"
@ -526,6 +685,31 @@ babel = ["Babel"]
lingua = ["lingua"] lingua = ["lingua"]
testing = ["pytest"] testing = ["pytest"]
[[package]]
name = "markdown-it-py"
version = "3.0.0"
description = "Python port of markdown-it. Markdown parsing, done right!"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
]
[package.dependencies]
mdurl = ">=0.1,<1.0"
[package.extras]
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
code-style = ["pre-commit (>=3.0,<4.0)"]
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
linkify = ["linkify-it-py (>=1,<3)"]
plugins = ["mdit-py-plugins"]
profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]] [[package]]
name = "markupsafe" name = "markupsafe"
version = "3.0.2" version = "3.0.2"
@ -597,6 +781,18 @@ files = [
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
] ]
[[package]]
name = "mdurl"
version = "0.1.2"
description = "Markdown URL utilities"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
]
[[package]] [[package]]
name = "multidict" name = "multidict"
version = "6.2.0" version = "6.2.0"
@ -992,6 +1188,21 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0
toml = ["tomli (>=2.0.1)"] toml = ["tomli (>=2.0.1)"]
yaml = ["pyyaml (>=6.0.1)"] yaml = ["pyyaml (>=6.0.1)"]
[[package]]
name = "pygments"
version = "2.19.1"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
]
[package.extras]
windows-terminal = ["colorama (>=0.4.6)"]
[[package]] [[package]]
name = "pyjwt" name = "pyjwt"
version = "1.7.1" version = "1.7.1"
@ -1052,6 +1263,145 @@ files = [
{file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"},
] ]
[[package]]
name = "pyyaml"
version = "6.0.2"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
]
[[package]]
name = "rich"
version = "14.0.0"
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
files = [
{file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"},
{file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"},
]
[package.dependencies]
markdown-it-py = ">=2.2.0"
pygments = ">=2.13.0,<3.0.0"
[package.extras]
jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "rich-toolkit"
version = "0.14.7"
description = "Rich toolkit for building command-line applications"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "rich_toolkit-0.14.7-py3-none-any.whl", hash = "sha256:def05cc6e0f1176d6263b6a26648f16a62c4563b277ca2f8538683acdba1e0da"},
{file = "rich_toolkit-0.14.7.tar.gz", hash = "sha256:6cca5a68850cc5778915f528eb785662c27ba3b4b2624612cce8340fa9701c5e"},
]
[package.dependencies]
click = ">=8.1.7"
rich = ">=13.7.1"
typing-extensions = ">=4.12.2"
[[package]]
name = "ruff"
version = "0.11.10"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["dev"]
files = [
{file = "ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58"},
{file = "ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed"},
{file = "ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f"},
{file = "ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125"},
{file = "ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad"},
{file = "ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19"},
{file = "ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224"},
{file = "ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1"},
{file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"},
]
[[package]]
name = "shellingham"
version = "1.5.4"
description = "Tool to Detect Surrounding Shell"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
]
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@ -1180,6 +1530,24 @@ anyio = ">=3.6.2,<5"
[package.extras] [package.extras]
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
[[package]]
name = "typer"
version = "0.16.0"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"},
{file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"},
]
[package.dependencies]
click = ">=8.0.0"
rich = ">=10.11.0"
shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"
@ -1221,11 +1589,234 @@ files = [
[package.dependencies] [package.dependencies]
click = ">=7.0" click = ">=7.0"
colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
h11 = ">=0.8" h11 = ">=0.8"
httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""}
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
[package.extras] [package.extras]
standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "uvloop"
version = "0.21.0"
description = "Fast implementation of asyncio event loop on top of libuv"
optional = false
python-versions = ">=3.8.0"
groups = ["main"]
markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
files = [
{file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"},
{file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"},
{file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"},
{file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"},
{file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"},
{file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"},
{file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"},
{file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"},
{file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"},
{file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"},
{file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"},
{file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"},
{file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"},
{file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"},
{file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"},
{file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"},
{file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"},
{file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"},
{file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"},
{file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"},
{file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"},
{file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"},
{file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"},
{file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"},
{file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"},
{file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"},
{file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"},
{file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"},
{file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"},
{file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"},
{file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"},
{file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"},
{file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"},
{file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"},
{file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"},
{file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"},
{file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"},
]
[package.extras]
dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"]
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
[[package]]
name = "watchfiles"
version = "1.0.5"
description = "Simple, modern and high performance file watching and code reload in python."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40"},
{file = "watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb"},
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11"},
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487"},
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256"},
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85"},
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358"},
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614"},
{file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f"},
{file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d"},
{file = "watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff"},
{file = "watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92"},
{file = "watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827"},
{file = "watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4"},
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d"},
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63"},
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418"},
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9"},
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6"},
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25"},
{file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5"},
{file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01"},
{file = "watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246"},
{file = "watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096"},
{file = "watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed"},
{file = "watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2"},
{file = "watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f"},
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec"},
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21"},
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512"},
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d"},
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6"},
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234"},
{file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2"},
{file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663"},
{file = "watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249"},
{file = "watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705"},
{file = "watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417"},
{file = "watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d"},
{file = "watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763"},
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40"},
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563"},
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04"},
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f"},
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a"},
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827"},
{file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a"},
{file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936"},
{file = "watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc"},
{file = "watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11"},
{file = "watchfiles-1.0.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2cfb371be97d4db374cba381b9f911dd35bb5f4c58faa7b8b7106c8853e5d225"},
{file = "watchfiles-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3904d88955fda461ea2531fcf6ef73584ca921415d5cfa44457a225f4a42bc1"},
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7a21715fb12274a71d335cff6c71fe7f676b293d322722fe708a9ec81d91f5"},
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dfd6ae1c385ab481766b3c61c44aca2b3cd775f6f7c0fa93d979ddec853d29d5"},
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b659576b950865fdad31fa491d31d37cf78b27113a7671d39f919828587b429b"},
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1909e0a9cd95251b15bff4261de5dd7550885bd172e3536824bf1cf6b121e200"},
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:832ccc221927c860e7286c55c9b6ebcc0265d5e072f49c7f6456c7798d2b39aa"},
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fbb6102b3296926d0c62cfc9347f6237fb9400aecd0ba6bbda94cae15f2b3b"},
{file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:15ac96dd567ad6c71c71f7b2c658cb22b7734901546cd50a475128ab557593ca"},
{file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b6227351e11c57ae997d222e13f5b6f1f0700d84b8c52304e8675d33a808382"},
{file = "watchfiles-1.0.5-cp39-cp39-win32.whl", hash = "sha256:974866e0db748ebf1eccab17862bc0f0303807ed9cda465d1324625b81293a18"},
{file = "watchfiles-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:9848b21ae152fe79c10dd0197304ada8f7b586d3ebc3f27f43c506e5a52a863c"},
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d"},
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034"},
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965"},
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57"},
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:554389562c29c2c182e3908b149095051f81d28c2fec79ad6c8997d7d63e0009"},
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a74add8d7727e6404d5dc4dcd7fac65d4d82f95928bbee0cf5414c900e86773e"},
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1489f25b051a89fae574505cc26360c8e95e227a9500182a7fe0afcc500ce0"},
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0901429650652d3f0da90bad42bdafc1f9143ff3605633c455c999a2d786cac"},
{file = "watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9"},
]
[package.dependencies]
anyio = ">=3.0.0"
[[package]]
name = "websockets"
version = "15.0.1"
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"},
{file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"},
{file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"},
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"},
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"},
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"},
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"},
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"},
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"},
{file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"},
{file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"},
{file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"},
{file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"},
{file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"},
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"},
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"},
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"},
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"},
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"},
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"},
{file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"},
{file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"},
{file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"},
{file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"},
{file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"},
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"},
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"},
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"},
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"},
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"},
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"},
{file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"},
{file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"},
{file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"},
{file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"},
{file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"},
{file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"},
{file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"},
{file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"},
{file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"},
{file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"},
{file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"},
{file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"},
{file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"},
{file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"},
{file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"},
{file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"},
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"},
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"},
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"},
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"},
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"},
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"},
{file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"},
{file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"},
{file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"},
{file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"},
{file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"},
{file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"},
]
[[package]] [[package]]
name = "win32-setctime" name = "win32-setctime"
version = "1.2.0" version = "1.2.0"
@ -1342,4 +1933,4 @@ propcache = ">=0.2.0"
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.11,<4.0" python-versions = ">=3.11,<4.0"
content-hash = "146687a6e082e27748cc339242d924d2fb0741f7f2eb842a025e137f5fb41378" content-hash = "5ed129fde2c5d7b3518fbb2fe2ce79f0bad2aa1060304d45a7bc26d35f7ab46b"

View File

@ -1,6 +1,6 @@
[project] [project]
name = "api" name = "api"
version = "0.0.2" version = "0.0.3"
description = "" description = ""
authors = [ authors = [
{name = "Vladislav",email = "vlad.dev@heado.ru"} {name = "Vladislav",email = "vlad.dev@heado.ru"}
@ -11,7 +11,7 @@ dependencies = [
"sqlalchemy[pymysql,aiomysql] (>=2.0.39,<3.0.0)", "sqlalchemy[pymysql,aiomysql] (>=2.0.39,<3.0.0)",
"alembic (>=1.15.1,<2.0.0)", "alembic (>=1.15.1,<2.0.0)",
"aio-pika (>=9.5.5,<10.0.0)", "aio-pika (>=9.5.5,<10.0.0)",
"fastapi[standart] (>=0.115.11,<0.116.0)", "fastapi[standard] (>=0.115.11,<0.116.0)",
"uvicorn (>=0.34.0,<0.35.0)", "uvicorn (>=0.34.0,<0.35.0)",
"loguru (>=0.7.3,<0.8.0)", "loguru (>=0.7.3,<0.8.0)",
"pydantic-settings (>=2.8.1,<3.0.0)", "pydantic-settings (>=2.8.1,<3.0.0)",
@ -25,3 +25,10 @@ dependencies = [
[build-system] [build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"] requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.group.dev.dependencies]
ruff = "^0.11.10"
[tool.ruff]
line-length = 120
extend-exclude = ["alembic"]