init commit
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
# Domain
|
||||||
|
DOMAIN={{sensitive_data}}
|
||||||
|
|
||||||
|
# Backend
|
||||||
|
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173,http://{{sensitive_data}},http://{{sensitive_data}}"
|
||||||
|
JWT_SECRET_KEY=postgresql+psycopg://{{sensitive_data}}:{{sensitive_data}}@localhost:5432/postgres
|
||||||
|
DEBUG=true
|
||||||
|
|
||||||
|
# Bot
|
||||||
|
TOKEN_TELEGRAM_API={{sensitive_data}}
|
||||||
|
WEB_APP_URL=https://{{sensitive_data}}
|
||||||
|
|
||||||
|
# Postgres
|
||||||
|
POSTGRES_SERVER=db
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=postgres
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=postgres
|
||||||
|
DATABASE_URL=postgresql+psycopg://{{sensitive_data}}:{{sensitive_data}}@db:5432/postgres
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
node_modules/
|
||||||
|
/test-results/
|
||||||
|
/playwright-report/
|
||||||
|
/blob-report/
|
||||||
|
/playwright/.cache/
|
||||||
|
.env
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
stages:
|
||||||
|
- build
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
variables:
|
||||||
|
DOMAIN: "{{sensitive_data}}"
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- echo "Setting up environment..."
|
||||||
|
- export $(grep -v '^#' .env.template | xargs)
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
stage: deploy
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
tags:
|
||||||
|
- vps
|
||||||
|
script:
|
||||||
|
- echo "Deploying application..."
|
||||||
|
- docker compose --env-file .env.template -f docker-compose.yml up -d --build --remove-orphans
|
||||||
|
only:
|
||||||
|
- master
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[settings]
|
||||||
|
profile=google
|
||||||
|
known_first_party=app
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
extend-exclude = [".github", "alembic"]
|
||||||
|
force-exclude = true
|
||||||
|
line-length = 79
|
||||||
|
show-fixes = true
|
||||||
|
src = ["src"]
|
||||||
|
target-version = "py312"
|
||||||
|
|
||||||
|
[format]
|
||||||
|
quote-style = "single"
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
[lint]
|
||||||
|
extend-select = [
|
||||||
|
"E", # pycodestyle errors
|
||||||
|
"W", # pycodestyle warnings
|
||||||
|
"F", # pyflakes
|
||||||
|
"B", # flake8-bugbear
|
||||||
|
"C4", # flake8-comprehensions
|
||||||
|
"UP", # pyupgrade
|
||||||
|
"ARG001", # unused arguments in functions
|
||||||
|
]
|
||||||
|
ignore = ["ANN101", "PERF101", "ISC001", "B008", "B904", "F821"]
|
||||||
|
|
||||||
|
[lint.extend-per-file-ignores]
|
||||||
|
"tests/**/*.py" = [
|
||||||
|
"S101", # Allow assert statements
|
||||||
|
"ARG",
|
||||||
|
"FBT",
|
||||||
|
]
|
||||||
|
"__init__.py" = [
|
||||||
|
"F403",
|
||||||
|
]
|
||||||
|
|
||||||
|
[lint.flake8-quotes]
|
||||||
|
inline-quotes = 'single'
|
||||||
|
|
||||||
|
[lint.flake8-tidy-imports]
|
||||||
|
ban-relative-imports = 'all'
|
||||||
|
|
||||||
|
[lint.mccabe]
|
||||||
|
max-complexity = 7
|
||||||
|
|
||||||
|
[lint.pyupgrade]
|
||||||
|
keep-runtime-typing = true
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__
|
||||||
|
app.egg-info
|
||||||
|
*.pyc
|
||||||
|
.mypy_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
.venv
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
__pycache__
|
||||||
|
app.egg-info
|
||||||
|
*.pyc
|
||||||
|
.mypy_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
.cache
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
FROM python:3.10
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
# Install uv
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:0.4.15 /uv /bin/uv
|
||||||
|
|
||||||
|
# Place executables in the environment at the front of the path
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Compile bytecode
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode
|
||||||
|
ENV UV_COMPILE_BYTECODE=1
|
||||||
|
|
||||||
|
# uv Cache
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching
|
||||||
|
ENV UV_LINK_MODE=copy
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
|
uv sync --frozen --no-install-project
|
||||||
|
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
|
||||||
|
COPY ./scripts /app/scripts
|
||||||
|
|
||||||
|
COPY ./pyproject.toml ./uv.lock ./alembic.ini /app/
|
||||||
|
|
||||||
|
COPY ./app /app/app
|
||||||
|
|
||||||
|
# Sync the project
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
CMD ["fastapi", "run", "--workers", "8", "app/main.py"]
|
||||||
Executable
+71
@@ -0,0 +1,71 @@
|
|||||||
|
# A generic, single database configuration.
|
||||||
|
|
||||||
|
[alembic]
|
||||||
|
# path to migration scripts
|
||||||
|
script_location = app/alembic
|
||||||
|
|
||||||
|
# template used to generate migration files
|
||||||
|
# file_template = %%(rev)s_%%(slug)s
|
||||||
|
|
||||||
|
# timezone to use when rendering the date
|
||||||
|
# within the migration file as well as the filename.
|
||||||
|
# string value is passed to dateutil.tz.gettz()
|
||||||
|
# leave blank for localtime
|
||||||
|
# timezone =
|
||||||
|
|
||||||
|
# max length of characters to apply to the
|
||||||
|
# "slug" field
|
||||||
|
#truncate_slug_length = 40
|
||||||
|
|
||||||
|
# set to 'true' to run the environment during
|
||||||
|
# the 'revision' command, regardless of autogenerate
|
||||||
|
# revision_environment = false
|
||||||
|
|
||||||
|
# set to 'true' to allow .pyc and .pyo files without
|
||||||
|
# a source .py file to be detected as revisions in the
|
||||||
|
# versions/ directory
|
||||||
|
# sourceless = false
|
||||||
|
|
||||||
|
# version location specification; this defaults
|
||||||
|
# to alembic/versions. When using multiple version
|
||||||
|
# directories, initial revisions must be specified with --version-path
|
||||||
|
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
|
||||||
|
|
||||||
|
# the output encoding used when revision files
|
||||||
|
# are written from script.py.mako
|
||||||
|
# output_encoding = utf-8
|
||||||
|
|
||||||
|
# Logging configuration
|
||||||
|
[loggers]
|
||||||
|
keys = root,sqlalchemy,alembic
|
||||||
|
|
||||||
|
[handlers]
|
||||||
|
keys = console
|
||||||
|
|
||||||
|
[formatters]
|
||||||
|
keys = generic
|
||||||
|
|
||||||
|
[logger_root]
|
||||||
|
level = WARN
|
||||||
|
handlers = console
|
||||||
|
qualname =
|
||||||
|
|
||||||
|
[logger_sqlalchemy]
|
||||||
|
level = WARN
|
||||||
|
handlers =
|
||||||
|
qualname = sqlalchemy.engine
|
||||||
|
|
||||||
|
[logger_alembic]
|
||||||
|
level = INFO
|
||||||
|
handlers =
|
||||||
|
qualname = alembic
|
||||||
|
|
||||||
|
[handler_console]
|
||||||
|
class = StreamHandler
|
||||||
|
args = (sys.stderr,)
|
||||||
|
level = NOTSET
|
||||||
|
formatter = generic
|
||||||
|
|
||||||
|
[formatter_generic]
|
||||||
|
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||||
|
datefmt = %H:%M:%S
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Generic single-database configuration.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
from logging.config import fileConfig
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
from sqlalchemy import engine_from_config
|
||||||
|
from sqlalchemy import pool
|
||||||
|
|
||||||
|
# this is the Alembic Config object, which provides
|
||||||
|
# access to the values within the .ini file in use.
|
||||||
|
config = context.config
|
||||||
|
|
||||||
|
# Interpret the config file for Python logging.
|
||||||
|
# This line sets up loggers basically.
|
||||||
|
if config.config_file_name is not None:
|
||||||
|
fileConfig(config.config_file_name)
|
||||||
|
|
||||||
|
# add your model's MetaData object here
|
||||||
|
# for 'autogenerate' support
|
||||||
|
# from myapp import mymodel
|
||||||
|
# target_metadata = mymodel.Base.metadata
|
||||||
|
|
||||||
|
from app.core.config import config as app_config
|
||||||
|
import app.models
|
||||||
|
|
||||||
|
target_metadata = app.models.SQLModel.metadata
|
||||||
|
config.set_main_option('sqlalchemy.url', app_config.DATABASE_URL)
|
||||||
|
|
||||||
|
# other values from the config, defined by the needs of env.py,
|
||||||
|
# can be acquired:
|
||||||
|
# my_important_option = config.get_main_option("my_important_option")
|
||||||
|
# ... etc.
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_offline() -> None:
|
||||||
|
"""Run migrations in 'offline' mode.
|
||||||
|
|
||||||
|
This configures the context with just a URL
|
||||||
|
and not an Engine, though an Engine is acceptable
|
||||||
|
here as well. By skipping the Engine creation
|
||||||
|
we don't even need a DBAPI to be available.
|
||||||
|
|
||||||
|
Calls to context.execute() here emit the given string to the
|
||||||
|
script output.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url = config.get_main_option("sqlalchemy.url")
|
||||||
|
context.configure(
|
||||||
|
url=url,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
literal_binds=True,
|
||||||
|
dialect_opts={"paramstyle": "named"},
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
def run_migrations_online() -> None:
|
||||||
|
"""Run migrations in 'online' mode.
|
||||||
|
|
||||||
|
In this scenario we need to create an Engine
|
||||||
|
and associate a connection with the context.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def process_revision_directives(context, revision, directives):
|
||||||
|
if config.cmd_opts.autogenerate:
|
||||||
|
script = directives[0]
|
||||||
|
if script.upgrade_ops.is_empty():
|
||||||
|
directives[:] = []
|
||||||
|
print('No changes in schema detected.')
|
||||||
|
|
||||||
|
connectable = engine_from_config(
|
||||||
|
config.get_section(config.config_ini_section),
|
||||||
|
prefix="sqlalchemy.",
|
||||||
|
poolclass=pool.NullPool,
|
||||||
|
)
|
||||||
|
|
||||||
|
with connectable.connect() as connection:
|
||||||
|
context.configure(
|
||||||
|
connection=connection,
|
||||||
|
target_metadata=target_metadata,
|
||||||
|
process_revision_directives=process_revision_directives
|
||||||
|
)
|
||||||
|
|
||||||
|
with context.begin_transaction():
|
||||||
|
context.run_migrations()
|
||||||
|
|
||||||
|
|
||||||
|
if context.is_offline_mode():
|
||||||
|
run_migrations_offline()
|
||||||
|
else:
|
||||||
|
run_migrations_online()
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
"""${message}
|
||||||
|
|
||||||
|
Revision ID: ${up_revision}
|
||||||
|
Revises: ${down_revision | comma,n}
|
||||||
|
Create Date: ${create_date}
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
${imports if imports else ""}
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = ${repr(up_revision)}
|
||||||
|
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||||
|
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
${upgrades if upgrades else "pass"}
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
${downgrades if downgrades else "pass"}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 0eb89d53f0d4
|
||||||
|
Revises: 2aac393edafe
|
||||||
|
Create Date: 2024-11-09 21:06:11.679952
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '0eb89d53f0d4'
|
||||||
|
down_revision: Union[str, None] = '2aac393edafe'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('event', sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('event', 'name')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
"""init
|
||||||
|
|
||||||
|
Revision ID: 2aac393edafe
|
||||||
|
Revises:
|
||||||
|
Create Date: 2024-11-09 20:45:26.289233
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '2aac393edafe'
|
||||||
|
down_revision: Union[str, None] = None
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('user',
|
||||||
|
sa.Column('id', sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column('username', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('event',
|
||||||
|
sa.Column('id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('owner_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('invite', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('eventuserlink',
|
||||||
|
sa.Column('event_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['event_id'], ['event.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('event_id', 'user_id')
|
||||||
|
)
|
||||||
|
op.create_table('transaction',
|
||||||
|
sa.Column('id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('payer_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('event_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('closed', sa.Boolean(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['event_id'], ['event.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['payer_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('item',
|
||||||
|
sa.Column('id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||||
|
sa.Column('price', sa.Float(), nullable=False),
|
||||||
|
sa.Column('transaction_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['transaction_id'], ['transaction.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_table('itemuserlink',
|
||||||
|
sa.Column('item_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['item_id'], ['item.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('item_id', 'user_id')
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('itemuserlink')
|
||||||
|
op.drop_table('item')
|
||||||
|
op.drop_table('transaction')
|
||||||
|
op.drop_table('eventuserlink')
|
||||||
|
op.drop_table('event')
|
||||||
|
op.drop_table('user')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
"""fix bigints
|
||||||
|
|
||||||
|
Revision ID: 3f6feff5972e
|
||||||
|
Revises: 0eb89d53f0d4
|
||||||
|
Create Date: 2024-11-10 11:49:31.092386
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '3f6feff5972e'
|
||||||
|
down_revision: Union[str, None] = '0eb89d53f0d4'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('event', 'owner_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
type_=sa.BigInteger(),
|
||||||
|
nullable=True)
|
||||||
|
op.alter_column('eventuserlink', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
type_=sa.BigInteger(),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.alter_column('itemuserlink', 'user_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
type_=sa.BigInteger(),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.alter_column('transaction', 'payer_id',
|
||||||
|
existing_type=sa.INTEGER(),
|
||||||
|
type_=sa.BigInteger(),
|
||||||
|
nullable=True)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('transaction', 'payer_id',
|
||||||
|
existing_type=sa.BigInteger(),
|
||||||
|
type_=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
op.alter_column('itemuserlink', 'user_id',
|
||||||
|
existing_type=sa.BigInteger(),
|
||||||
|
type_=sa.INTEGER(),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.alter_column('eventuserlink', 'user_id',
|
||||||
|
existing_type=sa.BigInteger(),
|
||||||
|
type_=sa.INTEGER(),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.alter_column('event', 'owner_id',
|
||||||
|
existing_type=sa.BigInteger(),
|
||||||
|
type_=sa.INTEGER(),
|
||||||
|
nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 4934e7406cd0
|
||||||
|
Revises: 3f6feff5972e
|
||||||
|
Create Date: 2024-11-10 11:50:17.038836
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = '4934e7406cd0'
|
||||||
|
down_revision: Union[str, None] = '3f6feff5972e'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
pass
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
from typing import Annotated
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from fastapi import Request
|
||||||
|
from fastapi.security import HTTPAuthorizationCredentials
|
||||||
|
from fastapi.security import HTTPBearer
|
||||||
|
import jwt
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
from .utils import decode_jwt
|
||||||
|
|
||||||
|
|
||||||
|
class BearerAuth(HTTPBearer):
|
||||||
|
def __init__(self, auto_error: bool = True):
|
||||||
|
super().__init__(auto_error=auto_error)
|
||||||
|
|
||||||
|
async def __call__(self, request: Request):
|
||||||
|
credentials: HTTPAuthorizationCredentials = await super().__call__(
|
||||||
|
request
|
||||||
|
)
|
||||||
|
|
||||||
|
if credentials:
|
||||||
|
if not credentials.scheme == 'Bearer':
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403, detail='Invalid authentication scheme.'
|
||||||
|
)
|
||||||
|
if not self.verify_jwt(credentials.credentials):
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403, detail='Invalid token or expired token.'
|
||||||
|
)
|
||||||
|
return credentials.credentials
|
||||||
|
else:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=403, detail='Invalid authorization code.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def verify_jwt(self, token: str) -> bool:
|
||||||
|
payload = decode_jwt(token)
|
||||||
|
is_valid = bool(payload)
|
||||||
|
|
||||||
|
return is_valid
|
||||||
|
|
||||||
|
|
||||||
|
oauth2_scheme = BearerAuth()
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
|
||||||
|
try:
|
||||||
|
payload = jwt.decode(
|
||||||
|
token, config.JWT_SECRET_KEY, algorithms=['HS256']
|
||||||
|
)
|
||||||
|
user = await User.get_or_create_user(
|
||||||
|
User(
|
||||||
|
id=payload['user_id'],
|
||||||
|
username=payload['username'],
|
||||||
|
events=[],
|
||||||
|
items=[],
|
||||||
|
transactions=[],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return user
|
||||||
|
except jwt.ExpiredSignatureError:
|
||||||
|
return {'error': 'Token is expired'}
|
||||||
|
except jwt.InvalidTokenError:
|
||||||
|
return {'error': 'Invalid token'}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
credentials_exception = fastapi.HTTPException(
|
||||||
|
status_code=fastapi.status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail='Could not validate credentials',
|
||||||
|
headers={'WWW-Authenticate': 'Bearer'},
|
||||||
|
)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
auth_router = fastapi.APIRouter()
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
import fastapi
|
||||||
|
|
||||||
|
from app.api.auth.routers import auth_router
|
||||||
|
import app.core.security.tokens
|
||||||
|
from app.models.base import BasicResponse
|
||||||
|
from app.models.telegram import TelegramInputData
|
||||||
|
from app.models.tokens import Token
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
@auth_router.post(
|
||||||
|
'/token',
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_401_UNAUTHORIZED: {
|
||||||
|
'description': 'Unauthorized',
|
||||||
|
'model': BasicResponse,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def authenticate(init_data: TelegramInputData) -> Token:
|
||||||
|
# if not config.DEBUG:
|
||||||
|
# fields = init_data.model_dump()
|
||||||
|
# sorted_fields = sorted(fields.items())
|
||||||
|
# formatted = [f'{key}={value}' for key, value in sorted_fields]
|
||||||
|
# data_check_string = '\n'.join(formatted)
|
||||||
|
|
||||||
|
# secret_key = hmac.new(
|
||||||
|
# config.TOKEN_TELEGRAM_API.encode(), b'WebAppData', sha256
|
||||||
|
# ).digest()
|
||||||
|
|
||||||
|
# if (
|
||||||
|
# hmac.new(
|
||||||
|
# data_check_string.encode(), secret_key, sha256
|
||||||
|
# ).hexdigest()
|
||||||
|
# != init_data.hash
|
||||||
|
# ):
|
||||||
|
# print(hmac.new(
|
||||||
|
# data_check_string.encode(), secret_key, sha256
|
||||||
|
# ).hexdigest())
|
||||||
|
# print(init_data.hash)
|
||||||
|
# raise HTTPException(status_code=403, detail='Unauthorized')
|
||||||
|
|
||||||
|
user = await User.get_or_create_user(
|
||||||
|
User(id=init_data.user.id, username=init_data.user.username)
|
||||||
|
)
|
||||||
|
|
||||||
|
return Token(
|
||||||
|
access_token=app.core.security.tokens.generate_token(
|
||||||
|
{'user_id': user.id, 'username': user.username},
|
||||||
|
expires_delta=timedelta(days=7),
|
||||||
|
),
|
||||||
|
token_type='bearer',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@auth_router.get('/ping')
|
||||||
|
def ping() -> str:
|
||||||
|
return 'pong'
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import jwt
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
|
||||||
|
|
||||||
|
def decode_jwt(token: str) -> dict:
|
||||||
|
try:
|
||||||
|
decoded_token = jwt.decode(
|
||||||
|
token, config.JWT_SECRET_KEY, algorithms=['HS256']
|
||||||
|
)
|
||||||
|
return decoded_token
|
||||||
|
|
||||||
|
except (jwt.InvalidTokenError, jwt.ExpiredSignatureError):
|
||||||
|
return {}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
calculate_debits_router = fastapi.APIRouter()
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from fastapi import Depends
|
||||||
|
from fastapi import HTTPException
|
||||||
|
import pydantic
|
||||||
|
import requests
|
||||||
|
from sqlmodel import select
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
from app.api.auth.deps import BearerAuth
|
||||||
|
from app.api.auth.deps import get_current_user
|
||||||
|
from app.core.db import engine
|
||||||
|
from app.models.event import Event
|
||||||
|
from app.models.user import User
|
||||||
|
from app.utils.telegram import send_telegram_message
|
||||||
|
|
||||||
|
from .routers import calculate_debits_router
|
||||||
|
|
||||||
|
|
||||||
|
class Debt(pydantic.BaseModel):
|
||||||
|
from_user_id: int
|
||||||
|
to_user_id: int
|
||||||
|
amount: float
|
||||||
|
|
||||||
|
|
||||||
|
@calculate_debits_router.post(
|
||||||
|
'/events/{event_id}',
|
||||||
|
description='Get debts smeta',
|
||||||
|
dependencies=[Depends(BearerAuth())],
|
||||||
|
)
|
||||||
|
def calculate_event_debts(
|
||||||
|
event_id: UUID, user: User = Depends(get_current_user)
|
||||||
|
) -> list[Debt]:
|
||||||
|
with Session(engine) as session:
|
||||||
|
result = session.execute(select(Event).where(Event.id == event_id))
|
||||||
|
event = result.scalars().first()
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail='Event not found')
|
||||||
|
|
||||||
|
if user.id != event.owner_id:
|
||||||
|
raise HTTPException(status_code=403, detail='Permission denied')
|
||||||
|
|
||||||
|
transactions = []
|
||||||
|
for transaction in event.transactions:
|
||||||
|
if not transaction.closed:
|
||||||
|
positions = [
|
||||||
|
{
|
||||||
|
'price': item.price,
|
||||||
|
# 'assigned_to_ids': [
|
||||||
|
# user.id for user in item.assigned_to
|
||||||
|
# ],
|
||||||
|
'assigned_to_ids': [user.id for user in event.users],
|
||||||
|
}
|
||||||
|
for item in transaction.items
|
||||||
|
]
|
||||||
|
transactions.append(
|
||||||
|
{'owner_id': transaction.payer_id, 'positions': positions}
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction.closed = True
|
||||||
|
|
||||||
|
if not transactions:
|
||||||
|
return []
|
||||||
|
|
||||||
|
participants = [{'id': user.id} for user in event.users]
|
||||||
|
input_data = {
|
||||||
|
'participants': participants,
|
||||||
|
'transactions': transactions,
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
'http://optimizetka:8000/optimizetka/api/calculate-debts',
|
||||||
|
json=input_data,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=response.status_code,
|
||||||
|
detail=f'Failed to calculate debts: {response.text}',
|
||||||
|
)
|
||||||
|
|
||||||
|
debts = response.json()
|
||||||
|
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
messages = {user.id: [] for user in event.users}
|
||||||
|
print(debts)
|
||||||
|
for debt in debts:
|
||||||
|
from_user = next(
|
||||||
|
u for u in event.users if u.id == debt['from_user_id']
|
||||||
|
)
|
||||||
|
to_user = next(
|
||||||
|
u for u in event.users if u.id == debt['to_user_id']
|
||||||
|
)
|
||||||
|
print(from_user, to_user)
|
||||||
|
amount = debt['amount']
|
||||||
|
messages[from_user].append(f'You owe {amount} to {to_user.name}.')
|
||||||
|
messages[to_user.id].append(f'{from_user.name} owes you {amount}.')
|
||||||
|
|
||||||
|
for user in event.users:
|
||||||
|
message_text = '\n'.join(messages[user.id])
|
||||||
|
if message_text:
|
||||||
|
send_telegram_message(user.chat_id, message_text)
|
||||||
|
|
||||||
|
return debts
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .invites import *
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
invites_router = fastapi.APIRouter()
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import typing
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import fastapi
|
||||||
|
from fastapi import HTTPException
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
import app.api.auth.deps
|
||||||
|
from app.api.auth.deps import BearerAuth
|
||||||
|
from app.api.events.invites.routers import invites_router
|
||||||
|
import app.core.db
|
||||||
|
from app.models import Event
|
||||||
|
import app.models.base
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
@invites_router.get(
|
||||||
|
'/{invite_id}',
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_400_BAD_REQUEST: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': 'Invalid invite',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dependencies=[fastapi.Depends(BearerAuth())],
|
||||||
|
)
|
||||||
|
def join_by_invite(
|
||||||
|
invite_id: uuid.UUID,
|
||||||
|
user: typing.Annotated[
|
||||||
|
User, fastapi.Depends(app.api.auth.deps.get_current_user)
|
||||||
|
],
|
||||||
|
) -> app.models.event.Event:
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
user = session.get(User, user.id)
|
||||||
|
event = session.query(Event).filter_by(invite=invite_id).first()
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND, detail='Invite not found'
|
||||||
|
)
|
||||||
|
|
||||||
|
event.users.append(user)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
return event
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
# from .invites.routers import invites_router
|
||||||
|
|
||||||
|
events_router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
# events_router.include_router(
|
||||||
|
# invites_router, prefix='/invites', tags=['invites']
|
||||||
|
# )
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import typing
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import fastapi
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
import app.api.auth.deps
|
||||||
|
from app.api.auth.deps import BearerAuth
|
||||||
|
from app.api.events.routers import events_router
|
||||||
|
import app.core.db
|
||||||
|
from app.models import Event
|
||||||
|
from app.models import OutputEvent
|
||||||
|
import app.models.event
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
@events_router.get(
|
||||||
|
'/',
|
||||||
|
response_model=list[app.models.event.OutputEvent],
|
||||||
|
description='Return events containing given user (by token)',
|
||||||
|
dependencies=[fastapi.Depends(BearerAuth())],
|
||||||
|
)
|
||||||
|
def list_events(
|
||||||
|
user: typing.Annotated[
|
||||||
|
User, fastapi.Depends(app.api.auth.deps.get_current_user)
|
||||||
|
],
|
||||||
|
):
|
||||||
|
output = []
|
||||||
|
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
user = session.get(User, user.id)
|
||||||
|
|
||||||
|
for event in user.events:
|
||||||
|
new_output = app.models.event.OutputEvent(
|
||||||
|
id=event.id,
|
||||||
|
owner=event.owner_id,
|
||||||
|
users=event.users,
|
||||||
|
invite=event.invite,
|
||||||
|
name=event.name,
|
||||||
|
)
|
||||||
|
output.append(new_output)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
@events_router.post(
|
||||||
|
'/add/{event_id}',
|
||||||
|
response_model=Event,
|
||||||
|
description='Add user to event',
|
||||||
|
dependencies=[fastapi.Depends(BearerAuth())],
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': r'Event \ user not found',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def add_to_event(
|
||||||
|
user: typing.Annotated[
|
||||||
|
User, fastapi.Depends(app.api.auth.deps.get_current_user)
|
||||||
|
],
|
||||||
|
event_id: uuid.UUID,
|
||||||
|
):
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(Event, event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Event not found', status_code=404
|
||||||
|
)
|
||||||
|
|
||||||
|
user = session.get(User, user.id)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='User not found', status_code=404
|
||||||
|
)
|
||||||
|
|
||||||
|
event.users.append(user)
|
||||||
|
|
||||||
|
session.add(event)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(event)
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
|
|
||||||
|
@events_router.post(
|
||||||
|
'/',
|
||||||
|
description='Create event',
|
||||||
|
dependencies=[fastapi.Depends(BearerAuth())],
|
||||||
|
)
|
||||||
|
def create_event(
|
||||||
|
event: app.models.event.BaseEvent,
|
||||||
|
user: typing.Annotated[
|
||||||
|
User, fastapi.Depends(app.api.auth.deps.get_current_user)
|
||||||
|
],
|
||||||
|
) -> app.models.event.Event:
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
new_event = app.models.event.Event(
|
||||||
|
name=event.name, owner_id=user.id, users=[user]
|
||||||
|
)
|
||||||
|
session.add(new_event)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_event)
|
||||||
|
|
||||||
|
return new_event
|
||||||
|
|
||||||
|
|
||||||
|
# @events_router.delete(
|
||||||
|
# '/{event_id}',
|
||||||
|
# dependencies=[fastapi.Depends(app.api.auth.deps.get_current_user)],
|
||||||
|
# description='Delete an event',
|
||||||
|
# )
|
||||||
|
# def delete_event(
|
||||||
|
# event_id: uuid.UUID,
|
||||||
|
# ):
|
||||||
|
# with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
# session.delete(session.get(app.models.event.Event, event_id))
|
||||||
|
# session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@events_router.get(
|
||||||
|
'/{event_id}',
|
||||||
|
response_model=app.models.event.OutputEvent,
|
||||||
|
dependencies=[fastapi.Depends(app.api.auth.deps.get_current_user)],
|
||||||
|
description='Get info for an event by the id',
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': r'Event \ user not found',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def event_by_id(event_id: uuid.UUID):
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(app.models.event.Event, event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Event not found', status_code=404
|
||||||
|
)
|
||||||
|
|
||||||
|
new_event = app.models.event.OutputEvent(
|
||||||
|
id=event.id,
|
||||||
|
owner=event.owner_id,
|
||||||
|
users=event.users,
|
||||||
|
invite=event.invite,
|
||||||
|
name=event.name,
|
||||||
|
)
|
||||||
|
return new_event
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
from fastapi import Depends
|
||||||
|
|
||||||
|
from app.api.auth.deps import BearerAuth
|
||||||
|
|
||||||
|
items_router = APIRouter(
|
||||||
|
dependencies=[Depends(BearerAuth())],
|
||||||
|
)
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
import fastapi
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
import app.core.db
|
||||||
|
from app.models.event import Event
|
||||||
|
from app.models.item import Item
|
||||||
|
from app.models.item import ItemRequest
|
||||||
|
from app.models.item import OutputItem
|
||||||
|
from app.models.transactions import Transaction
|
||||||
|
|
||||||
|
from ...models import BasicResponse
|
||||||
|
from ..auth.deps import BearerAuth
|
||||||
|
from .routers import items_router
|
||||||
|
|
||||||
|
|
||||||
|
@items_router.get(
|
||||||
|
'/{transaction_id}',
|
||||||
|
description='Get items from transaction',
|
||||||
|
responses={
|
||||||
|
404: {
|
||||||
|
'model': BasicResponse,
|
||||||
|
'description': 'Transaction by this ID is not found',
|
||||||
|
},
|
||||||
|
403: {'model': BasicResponse, 'description': 'Unauthorized'},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_items(transaction_id: UUID) -> list[OutputItem]:
|
||||||
|
with sqlmodel.Session(bind=app.core.db.engine) as session:
|
||||||
|
transaction = session.get(Transaction, transaction_id)
|
||||||
|
if not transaction:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Transaction not found', status_code=404
|
||||||
|
)
|
||||||
|
|
||||||
|
out = []
|
||||||
|
for ti in transaction.items:
|
||||||
|
out.append(OutputItem(
|
||||||
|
title=ti.title,
|
||||||
|
id=ti.id,
|
||||||
|
price=ti.price,
|
||||||
|
assigned_to=ti.assigned_to,
|
||||||
|
transaction_id=ti.transaction_id
|
||||||
|
))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
@items_router.post(
|
||||||
|
'/{transaction_id}',
|
||||||
|
description='Create item in transaction',
|
||||||
|
responses={
|
||||||
|
400: {
|
||||||
|
'model': BasicResponse,
|
||||||
|
'description': 'Invalid data format (ex. negative price)',
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
'model': BasicResponse,
|
||||||
|
'description': 'Transaction with this ID is not found',
|
||||||
|
},
|
||||||
|
403: {'model': BasicResponse, 'description': 'Unauthorized'},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def create_item(item: ItemRequest, transaction_id: UUID) -> OutputItem:
|
||||||
|
if item.price <= 0:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Price cannot be null or negative', status_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
with sqlmodel.Session(bind=app.core.db.engine) as session:
|
||||||
|
transaction = session.get(Transaction, transaction_id)
|
||||||
|
if not transaction:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Transaction not found', status_code=404
|
||||||
|
)
|
||||||
|
event = session.get(Event, transaction.event_id)
|
||||||
|
|
||||||
|
new_item = Item(
|
||||||
|
title=item.title,
|
||||||
|
price=item.price,
|
||||||
|
assigned_to=[] if not item.all_users_selected else event.users,
|
||||||
|
transaction_id=transaction.id,
|
||||||
|
)
|
||||||
|
session.add(new_item)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(new_item)
|
||||||
|
|
||||||
|
return OutputItem(
|
||||||
|
title=item.title,
|
||||||
|
id=item.id,
|
||||||
|
price=item.price,
|
||||||
|
assigned_to=item.assigned_to,
|
||||||
|
transaction_id=item.transaction_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@items_router.get(
|
||||||
|
path='/{transaction_id}/{item_id}',
|
||||||
|
description='Get item by itself ID',
|
||||||
|
dependencies=[fastapi.Depends(BearerAuth())],
|
||||||
|
responses={
|
||||||
|
404: {
|
||||||
|
'model': BasicResponse,
|
||||||
|
'description': 'Item with this ID is not found',
|
||||||
|
},
|
||||||
|
403: {'model': BasicResponse, 'description': 'Unauthorized'},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_item(transaction_id: UUID, item_id: UUID) -> OutputItem:
|
||||||
|
with sqlmodel.Session(bind=app.core.db.engine) as session:
|
||||||
|
item = session.get(Item, item_id)
|
||||||
|
if not item:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Item not found', status_code=404
|
||||||
|
)
|
||||||
|
|
||||||
|
return OutputItem(
|
||||||
|
id=item.id,
|
||||||
|
title=item.title,
|
||||||
|
price=item.price,
|
||||||
|
assigned_to=item.assigned_to,
|
||||||
|
transaction_id=transaction_id,
|
||||||
|
)
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
import app.api.auth.routers
|
||||||
|
import app.api.calculate_debits.routers
|
||||||
|
import app.api.events.routers
|
||||||
|
import app.api.items.routers
|
||||||
|
import app.api.transactions.routers
|
||||||
|
import app.api.utils.routers
|
||||||
|
|
||||||
|
api_router = fastapi.APIRouter()
|
||||||
|
|
||||||
|
api_router.include_router(
|
||||||
|
app.api.auth.routers.auth_router, prefix='/auth', tags=['auth']
|
||||||
|
)
|
||||||
|
api_router.include_router(
|
||||||
|
app.api.utils.routers.utils_router, prefix='/utils', tags=['utils']
|
||||||
|
)
|
||||||
|
api_router.include_router(
|
||||||
|
app.api.events.routers.events_router, prefix='/events', tags=['events']
|
||||||
|
)
|
||||||
|
api_router.include_router(
|
||||||
|
app.api.transactions.routers.transactions_router,
|
||||||
|
prefix='/transaction',
|
||||||
|
tags=['transactions'],
|
||||||
|
)
|
||||||
|
api_router.include_router(
|
||||||
|
app.api.calculate_debits.routers.calculate_debits_router,
|
||||||
|
prefix='/calculate_debits',
|
||||||
|
tags=['calculate_debits'],
|
||||||
|
)
|
||||||
|
api_router.include_router(
|
||||||
|
app.api.items.routers.items_router, prefix='/items', tags=['items']
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
transactions_router = APIRouter()
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
import typing
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
import fastapi
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
import app.api.auth.deps
|
||||||
|
from app.api.transactions.routers import transactions_router
|
||||||
|
import app.core.db
|
||||||
|
import app.models.item
|
||||||
|
|
||||||
|
|
||||||
|
@transactions_router.get(
|
||||||
|
'/{event_id}/{transaction_id}',
|
||||||
|
response_model=app.models.transactions.OutputTransaction,
|
||||||
|
description='Get a transaction by its event and transaction id',
|
||||||
|
dependencies=[fastapi.Depends(app.api.auth.deps.get_current_user)],
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': r'Event \ transaction not found',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_transaction(
|
||||||
|
event_id: uuid.UUID,
|
||||||
|
transaction_id: uuid.UUID,
|
||||||
|
):
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(app.models.event.Event, event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404, detail='Event not found'
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction = session.get(
|
||||||
|
app.models.transactions.Transaction, transaction_id
|
||||||
|
)
|
||||||
|
output_transaction = app.models.transactions.OutputTransaction(
|
||||||
|
title=transaction.title,
|
||||||
|
payer=transaction.payer,
|
||||||
|
event_id=transaction.event_id,
|
||||||
|
closed=transaction.closed,
|
||||||
|
items=transaction.items,
|
||||||
|
id=transaction.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return output_transaction
|
||||||
|
|
||||||
|
|
||||||
|
@transactions_router.get(
|
||||||
|
'/{event_id}',
|
||||||
|
description='Get all transactions of an event',
|
||||||
|
dependencies=[fastapi.Depends(app.api.auth.deps.get_current_user)],
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': r'Event not found',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def list_transactions(
|
||||||
|
event_id: uuid.UUID,
|
||||||
|
) -> list[app.models.transactions.OutputTransaction]:
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(app.models.event.Event, event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404, detail='Event not found'
|
||||||
|
)
|
||||||
|
|
||||||
|
output = []
|
||||||
|
|
||||||
|
for transaction in event.transactions:
|
||||||
|
output.append(
|
||||||
|
app.models.transactions.OutputTransaction(
|
||||||
|
title=transaction.title,
|
||||||
|
payer=transaction.payer,
|
||||||
|
event_id=transaction.event_id,
|
||||||
|
closed=transaction.closed,
|
||||||
|
items=transaction.items,
|
||||||
|
id=transaction.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
@transactions_router.post(
|
||||||
|
'/{event_id}',
|
||||||
|
response_model=app.models.transactions.OutputTransaction,
|
||||||
|
description='Create a transaction by a event id',
|
||||||
|
dependencies=[fastapi.Depends(app.api.auth.deps.get_current_user)],
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': 'Event not found',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def create_transaction(
|
||||||
|
title: typing.Annotated[
|
||||||
|
str, fastapi.Body(description='Title of transaction')
|
||||||
|
],
|
||||||
|
event_id: uuid.UUID,
|
||||||
|
user: typing.Annotated[
|
||||||
|
app.models.user.User,
|
||||||
|
fastapi.Depends(app.api.auth.deps.get_current_user),
|
||||||
|
],
|
||||||
|
):
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(app.models.event.Event, event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404, detail='Event not found'
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction = app.models.transactions.Transaction(
|
||||||
|
title=title,
|
||||||
|
payer_id=user.id,
|
||||||
|
event_id=event.id,
|
||||||
|
items=[],
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add(transaction)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(transaction)
|
||||||
|
|
||||||
|
output_transaction = app.models.transactions.OutputTransaction(
|
||||||
|
id=transaction.id,
|
||||||
|
title=transaction.title,
|
||||||
|
payer=transaction.payer,
|
||||||
|
event_id=transaction.event_id,
|
||||||
|
closed=transaction.closed,
|
||||||
|
items=transaction.items,
|
||||||
|
)
|
||||||
|
|
||||||
|
return output_transaction
|
||||||
|
|
||||||
|
|
||||||
|
@transactions_router.post(
|
||||||
|
'/{event_id}/{transaction_id}/items',
|
||||||
|
response_model=app.models.item.Item,
|
||||||
|
description='Create a transaction by a event id',
|
||||||
|
dependencies=[fastapi.Depends(app.api.auth.deps.get_current_user)],
|
||||||
|
responses={
|
||||||
|
fastapi.status.HTTP_404_NOT_FOUND: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'detail': r'Event \ transaction not found',
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'model': app.models.base.BasicResponse,
|
||||||
|
'description': 'Invalid data format (ex. negative price)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def add_item_to_transaction(
|
||||||
|
event_id: uuid.UUID,
|
||||||
|
transaction_id: uuid.UUID,
|
||||||
|
title: str = fastapi.Body(description='Title of item'),
|
||||||
|
price: float = fastapi.Body(description='Price of item'),
|
||||||
|
add_all_users: bool = fastapi.Body(
|
||||||
|
description='Assign all users to this item', default=False
|
||||||
|
),
|
||||||
|
):
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(app.models.event.Event, event_id)
|
||||||
|
|
||||||
|
if not event:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404, detail='Event not found'
|
||||||
|
)
|
||||||
|
|
||||||
|
transaction = session.get(
|
||||||
|
app.models.transactions.Transaction, transaction_id
|
||||||
|
)
|
||||||
|
|
||||||
|
if not transaction:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404, detail='Transaction not found'
|
||||||
|
)
|
||||||
|
|
||||||
|
if transaction.event_id != event.id:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail='This transaction is not a child of this event',
|
||||||
|
)
|
||||||
|
|
||||||
|
item = app.models.item.Item(
|
||||||
|
title=title,
|
||||||
|
price=price,
|
||||||
|
assigned_to=[] if not add_all_users else event.users,
|
||||||
|
transaction_id=transaction_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
if item.price <= 0:
|
||||||
|
raise fastapi.HTTPException(
|
||||||
|
detail='Price cannot be null or negative', status_code=400
|
||||||
|
)
|
||||||
|
|
||||||
|
session.add(item)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(item)
|
||||||
|
|
||||||
|
return item
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all routes
|
||||||
|
from .routes import *
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import fastapi
|
||||||
|
|
||||||
|
utils_router = fastapi.APIRouter()
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import fastapi
|
||||||
|
from fastapi import HTTPException
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
from app.api.auth.deps import BearerAuth
|
||||||
|
from app.api.auth.deps import get_current_user
|
||||||
|
from app.api.utils.routers import utils_router
|
||||||
|
import app.core.db
|
||||||
|
from app.models import BasicResponse
|
||||||
|
from app.models import User
|
||||||
|
from app.models.base import OfdRequest
|
||||||
|
from app.models.event import Event
|
||||||
|
from app.models.item import Item
|
||||||
|
from app.models.transactions import Transaction
|
||||||
|
from app.utils.nalog import get_nalog_data
|
||||||
|
|
||||||
|
|
||||||
|
@utils_router.get('/health-check')
|
||||||
|
def health_check() -> dict[str, str]:
|
||||||
|
return {'msg': 'healthy'}
|
||||||
|
|
||||||
|
|
||||||
|
@utils_router.post(
|
||||||
|
'/ofd',
|
||||||
|
description='Get items info from OFD bare string',
|
||||||
|
dependencies=[fastapi.Depends(BearerAuth())],
|
||||||
|
responses={
|
||||||
|
400: {'model': BasicResponse, 'description': 'Bad OFD data'},
|
||||||
|
403: {'model': BasicResponse, 'description': 'Unauthorized'},
|
||||||
|
404: {'model': BasicResponse, 'description': 'Event not found'},
|
||||||
|
500: {
|
||||||
|
'model': BasicResponse,
|
||||||
|
'description': 'Error while processing OFD data',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def ofd(
|
||||||
|
ofd: OfdRequest, user: User = fastapi.Depends(get_current_user)
|
||||||
|
) -> Transaction:
|
||||||
|
with sqlmodel.Session(bind=app.core.db.engine) as session:
|
||||||
|
event = session.get(Event, ofd.event_id)
|
||||||
|
if not event:
|
||||||
|
raise HTTPException(status_code=404, detail='Event not found')
|
||||||
|
|
||||||
|
data = await get_nalog_data(ofd.ofd_string)
|
||||||
|
data = data['data']['json']
|
||||||
|
if not data:
|
||||||
|
raise HTTPException(status_code=400, detail='Bad OFD data')
|
||||||
|
|
||||||
|
transaction = Transaction(
|
||||||
|
payer_id=user.id, event_id=event.id, title=data['retailPlace']
|
||||||
|
)
|
||||||
|
session.add(transaction)
|
||||||
|
session.commit()
|
||||||
|
items = data['items']
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
new_item = Item(
|
||||||
|
title=item['name'],
|
||||||
|
price=item['sum'] / 100,
|
||||||
|
transaction_id=transaction.id,
|
||||||
|
)
|
||||||
|
session.add(new_item)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
transaction.items.append(new_item)
|
||||||
|
session.add(transaction)
|
||||||
|
session.refresh(transaction)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return transaction
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(
|
||||||
|
detail='Error while getting information about check',
|
||||||
|
status_code=500,
|
||||||
|
)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import secrets
|
||||||
|
|
||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
|
||||||
|
class Config(pydantic_settings.BaseSettings):
|
||||||
|
model_config = pydantic_settings.SettingsConfigDict(
|
||||||
|
env_file='../.env',
|
||||||
|
env_ignore_empty=True,
|
||||||
|
extra='ignore',
|
||||||
|
)
|
||||||
|
|
||||||
|
DATABASE_URL: str = pydantic.fields.Field(default=None)
|
||||||
|
TOKEN_TELEGRAM_API: str = pydantic.fields.Field(default=None)
|
||||||
|
|
||||||
|
JWT_SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||||
|
JWT_ALGORITHM: str = 'HS256'
|
||||||
|
SAMPLE_PAYLOAD: dict[str, str] = pydantic.fields.Field(
|
||||||
|
default={}, exclude=True
|
||||||
|
)
|
||||||
|
TOKEN_TELEGRAM_API: str = pydantic.fields.Field(default=None)
|
||||||
|
DEBUG: bool = pydantic.fields.Field(default=False)
|
||||||
|
|
||||||
|
|
||||||
|
config = Config()
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
from app.utils.factories import UserFactory
|
||||||
|
|
||||||
|
engine = sqlmodel.create_engine(config.DATABASE_URL)
|
||||||
|
|
||||||
|
|
||||||
|
def fill_with_sample_data():
|
||||||
|
users = [
|
||||||
|
{'id': 0, 'username': 'petr'},
|
||||||
|
{'id': 1, 'username': 'aleksandr'},
|
||||||
|
{'id': 2, 'username': 'seva'},
|
||||||
|
{'id': 3, 'username': 'maksim'},
|
||||||
|
{'id': 4, 'username': 'timur'},
|
||||||
|
{'id': 5, 'username': 'roman'},
|
||||||
|
]
|
||||||
|
UserFactory.bulk_new_users(users)
|
||||||
|
|
||||||
|
events = []
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import passlib.context
|
||||||
|
|
||||||
|
pwd_context = passlib.context.CryptContext(schemes=['bcrypt'])
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||||
|
return pwd_context.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
|
||||||
|
def get_password_hash(password: str) -> str:
|
||||||
|
return pwd_context.hash(password)
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import datetime
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import jwt
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
import app.models
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
def generate_token(
|
||||||
|
payload: dict[typing.Any, typing.Any],
|
||||||
|
expires_delta: datetime.timedelta | None = None,
|
||||||
|
) -> str:
|
||||||
|
to_encode = payload.copy()
|
||||||
|
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.datetime.now() + expires_delta
|
||||||
|
|
||||||
|
else:
|
||||||
|
expire = datetime.datetime.now() + datetime.timedelta(minutes=15)
|
||||||
|
|
||||||
|
to_encode.update({'exp': expire})
|
||||||
|
encoded_jwt = jwt.encode(
|
||||||
|
to_encode, config.JWT_SECRET_KEY, algorithm=config.JWT_ALGORITHM
|
||||||
|
)
|
||||||
|
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
|
||||||
|
def user_by_token(token: str) -> User | None:
|
||||||
|
"""Expects user_id to be in token payload"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload: dict = jwt.decode(
|
||||||
|
token, config.JWT_SECRET_KEY, [config.JWT_ALGORITHM]
|
||||||
|
)
|
||||||
|
|
||||||
|
except (jwt.InvalidTokenError, jwt.InvalidSignatureError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
user_id: str = payload.get('user_id')
|
||||||
|
|
||||||
|
if not user_id.isdigit():
|
||||||
|
return None
|
||||||
|
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
user = session.get(User, user_id)
|
||||||
|
|
||||||
|
return user
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import fastapi
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
|
||||||
|
import app.api.routers
|
||||||
|
|
||||||
|
app_router = fastapi.FastAPI()
|
||||||
|
app_router.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=['*'],
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=['*'],
|
||||||
|
allow_headers=['*'],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
app_router.include_router(
|
||||||
|
app.api.routers.api_router,
|
||||||
|
prefix='/api',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# async def lifespan(app: fastapi.FastAPI):
|
||||||
|
# fill_with_sample_data()
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
__all__ = ['SQLModel']
|
||||||
|
|
||||||
|
# Initialize all models for SQLModel's __init_subclass__ to trigger
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
from .base import *
|
||||||
|
from .event import *
|
||||||
|
from .item import *
|
||||||
|
from .ofd import *
|
||||||
|
from .telegram import *
|
||||||
|
from .tokens import *
|
||||||
|
from .transactions import *
|
||||||
|
from .user import *
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class BasicResponse(BaseModel):
|
||||||
|
detail: str
|
||||||
|
|
||||||
|
|
||||||
|
class OfdRequest(BaseModel):
|
||||||
|
ofd_string: str
|
||||||
|
event_id: UUID
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
from sqlmodel import Field
|
||||||
|
from sqlmodel import Relationship
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
from app.models.transactions import Transaction
|
||||||
|
|
||||||
|
from .links import EventUserLink
|
||||||
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEvent(SQLModel):
|
||||||
|
name: str = Field(nullable=False, default='Change name pls')
|
||||||
|
|
||||||
|
|
||||||
|
class Event(BaseEvent, table=True):
|
||||||
|
id: UUID = Field(primary_key=True, default_factory=uuid4)
|
||||||
|
owner_id: int = Field(
|
||||||
|
sa_column=Column(BigInteger(), ForeignKey('user.id'))
|
||||||
|
)
|
||||||
|
invite: str | None = Field(default_factory=uuid4)
|
||||||
|
users: list['User'] = Relationship(
|
||||||
|
back_populates='events', link_model=EventUserLink
|
||||||
|
)
|
||||||
|
transactions: list['Transaction'] = Relationship(
|
||||||
|
back_populates='event', cascade_delete=True
|
||||||
|
)
|
||||||
|
|
||||||
|
async def add_user(self, user: User):
|
||||||
|
self.users.append(user)
|
||||||
|
|
||||||
|
async def add_transaction(self, transaction: Transaction):
|
||||||
|
self.transactions.append(transaction)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputEvent(BaseEvent):
|
||||||
|
id: UUID
|
||||||
|
owner: int
|
||||||
|
users: list[User]
|
||||||
|
invite: str | None
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class AddUserRequest(SQLModel):
|
||||||
|
user_id: int
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlmodel import Field
|
||||||
|
from sqlmodel import Relationship
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
from .links import ItemUserLink
|
||||||
|
|
||||||
|
|
||||||
|
class Item(SQLModel, table=True):
|
||||||
|
id: UUID = Field(primary_key=True, default_factory=uuid4)
|
||||||
|
title: str = Field(nullable=False)
|
||||||
|
price: float = Field(nullable=False)
|
||||||
|
|
||||||
|
assigned_to: list['User'] = Relationship(
|
||||||
|
back_populates='items', link_model=ItemUserLink
|
||||||
|
)
|
||||||
|
transaction_id: UUID = Field(foreign_key='transaction.id')
|
||||||
|
transaction: 'Transaction' = Relationship(back_populates='items')
|
||||||
|
|
||||||
|
def assign_user(self, user: User):
|
||||||
|
self.assigned_to.append(user)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputItem(SQLModel):
|
||||||
|
id: UUID
|
||||||
|
title: str
|
||||||
|
price: float
|
||||||
|
assigned_to: list['User']
|
||||||
|
transaction_id: UUID
|
||||||
|
|
||||||
|
|
||||||
|
class ItemRequest(SQLModel):
|
||||||
|
title: str
|
||||||
|
price: float
|
||||||
|
|
||||||
|
all_users_selected: bool = False
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
|
||||||
|
class EventUserLink(sqlmodel.SQLModel, table=True):
|
||||||
|
event_id: uuid.UUID | None = sqlmodel.Field(
|
||||||
|
default=None, foreign_key='event.id', primary_key=True
|
||||||
|
)
|
||||||
|
user_id: int | None = sqlmodel.Field(
|
||||||
|
sa_column=Column(BigInteger(), ForeignKey('user.id'), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemUserLink(sqlmodel.SQLModel, table=True):
|
||||||
|
item_id: uuid.UUID | None = sqlmodel.Field(
|
||||||
|
default=None, foreign_key='item.id', primary_key=True
|
||||||
|
)
|
||||||
|
user_id: int | None = sqlmodel.Field(
|
||||||
|
sa_column=Column(BigInteger(), ForeignKey('user.id'), primary_key=True)
|
||||||
|
)
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class OfdRequest(BaseModel):
|
||||||
|
qr_data: str
|
||||||
|
|
||||||
|
|
||||||
|
class Item(BaseModel):
|
||||||
|
name: str
|
||||||
|
price: int
|
||||||
|
quantity: int
|
||||||
|
sum: int
|
||||||
|
|
||||||
|
|
||||||
|
class Data(BaseModel):
|
||||||
|
user: str
|
||||||
|
retailPlaceAddress: str
|
||||||
|
userInn: str
|
||||||
|
ticketDate: str
|
||||||
|
requestNumber: int
|
||||||
|
shiftNumber: int
|
||||||
|
operator: str
|
||||||
|
operationType: int
|
||||||
|
items: list[Item]
|
||||||
|
nds18: int | None
|
||||||
|
nds10: int | None
|
||||||
|
nds0: int | None
|
||||||
|
ndsNo: int | None
|
||||||
|
totalSum: int
|
||||||
|
cashTotalSum: int | None
|
||||||
|
ecashTotalSum: int | None
|
||||||
|
taxationType: int
|
||||||
|
kktRegId: str
|
||||||
|
kktNumber: str
|
||||||
|
fiscalDriveNumber: str
|
||||||
|
fiscalDocumentNumber: int
|
||||||
|
fiscalSign: int
|
||||||
|
|
||||||
|
|
||||||
|
class RequestManual(BaseModel):
|
||||||
|
fn: str
|
||||||
|
fd: str
|
||||||
|
fp: str
|
||||||
|
check_time: str
|
||||||
|
type: int
|
||||||
|
sum: float
|
||||||
|
|
||||||
|
|
||||||
|
class Request(BaseModel):
|
||||||
|
qrurl: str
|
||||||
|
qrfile: str
|
||||||
|
qrraw: str
|
||||||
|
manual: RequestManual
|
||||||
|
|
||||||
|
|
||||||
|
class OfdResponse(BaseModel):
|
||||||
|
code: int
|
||||||
|
first: int
|
||||||
|
data: Data
|
||||||
|
html: str
|
||||||
|
request: Request
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramWebUser(BaseModel):
|
||||||
|
id: int
|
||||||
|
first_name: str
|
||||||
|
last_name: str | None = ''
|
||||||
|
username: str | None = ''
|
||||||
|
language_code: str | None = ''
|
||||||
|
is_premium: bool = False
|
||||||
|
added_to_attachment_menu: bool = False
|
||||||
|
allows_write_to_pm: bool = False
|
||||||
|
photo_url: str | None = ''
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramInputData(BaseModel):
|
||||||
|
query_id: str | None = ''
|
||||||
|
user: TelegramWebUser
|
||||||
|
receiver: Any = ''
|
||||||
|
chat: Any = ''
|
||||||
|
chat_type: str | None = ''
|
||||||
|
chat_instance: str | None = ''
|
||||||
|
start_param: str | None = ''
|
||||||
|
can_send_after: int | None = ''
|
||||||
|
auth_date: int
|
||||||
|
hash: str
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import pydantic
|
||||||
|
|
||||||
|
|
||||||
|
class Token(pydantic.BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from uuid import UUID
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
from sqlalchemy import BigInteger
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlalchemy import ForeignKey
|
||||||
|
from sqlmodel import Field
|
||||||
|
from sqlmodel import Relationship
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
from app.models.item import Item
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTransaction(SQLModel):
|
||||||
|
title: str = Field(nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction(BaseTransaction, table=True):
|
||||||
|
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||||
|
payer_id: int = Field(
|
||||||
|
sa_column=Column(BigInteger(), ForeignKey('user.id'))
|
||||||
|
)
|
||||||
|
payer: User = Relationship(back_populates='transactions')
|
||||||
|
event_id: UUID = Field(foreign_key='event.id')
|
||||||
|
event: 'Event' = Relationship(back_populates='transactions')
|
||||||
|
closed: bool = Field(nullable=False, default=False)
|
||||||
|
items: list['Item'] = Relationship(back_populates='transaction')
|
||||||
|
|
||||||
|
async def add_participant(self, participant: User):
|
||||||
|
self.participants.append(participant)
|
||||||
|
|
||||||
|
async def add_item(self, item: Item):
|
||||||
|
self.items.append(item)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputTransaction(BaseTransaction):
|
||||||
|
id: UUID
|
||||||
|
payer: User
|
||||||
|
event_id: UUID
|
||||||
|
closed: bool
|
||||||
|
items: list['Item']
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from sqlalchemy import BigInteger
|
||||||
|
from sqlalchemy import Column
|
||||||
|
from sqlmodel import create_engine
|
||||||
|
from sqlmodel import Field
|
||||||
|
from sqlmodel import Relationship
|
||||||
|
from sqlmodel import Session
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
|
||||||
|
from .links import ItemUserLink
|
||||||
|
|
||||||
|
|
||||||
|
class User(SQLModel, table=True):
|
||||||
|
id: int = Field(sa_column=Column(BigInteger(), primary_key=True))
|
||||||
|
username: str = Field(nullable=False)
|
||||||
|
events: list['Event'] = Relationship(back_populates='users')
|
||||||
|
items: list['Item'] = Relationship(
|
||||||
|
back_populates='assigned_to', link_model=ItemUserLink
|
||||||
|
)
|
||||||
|
transactions: list['Transaction'] = Relationship(back_populates='payer')
|
||||||
|
|
||||||
|
async def get_or_create_user(self):
|
||||||
|
engine = create_engine(url=config.DATABASE_URL)
|
||||||
|
with Session(engine) as session:
|
||||||
|
user = session.get(User, self.id)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
user = User(
|
||||||
|
id=self.id,
|
||||||
|
username=self.username,
|
||||||
|
)
|
||||||
|
session.add(user)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
session.refresh(user)
|
||||||
|
|
||||||
|
return user
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def auth_token():
|
||||||
|
pass
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from fastapi.testclient import TestClient
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
from app.api.events.routers import events_router
|
||||||
|
import app.core.db
|
||||||
|
from app.models.event import Event
|
||||||
|
from app.tests.api.mockdata import auth_client
|
||||||
|
from app.tests.api.mockdata import telegram_init_data
|
||||||
|
|
||||||
|
client = TestClient(events_router)
|
||||||
|
|
||||||
|
|
||||||
|
def test_events() -> None:
|
||||||
|
auth_response = auth_client.post('/token', json=telegram_init_data)
|
||||||
|
assert auth_response.status_code == 200, 'Failed to authenticate'
|
||||||
|
|
||||||
|
token = auth_response.json().get('access_token')
|
||||||
|
assert token is not None, 'Token not found in response'
|
||||||
|
|
||||||
|
headers = {'Authorization': f'Bearer {token}'}
|
||||||
|
|
||||||
|
event_data = {'name': 'Sample Event Name'}
|
||||||
|
|
||||||
|
response = client.post('/', headers=headers, json=event_data)
|
||||||
|
response_event = response.json()
|
||||||
|
|
||||||
|
assert response.status_code == 200, 'Event creation failed'
|
||||||
|
|
||||||
|
with sqlmodel.Session(app.core.db.engine) as session:
|
||||||
|
event = session.get(Event, response_event['id'])
|
||||||
|
assert event.name == event_data['name'], 'Wrong name for event'
|
||||||
|
|
||||||
|
response = client.get(f'/{response_event["id"]}', headers=headers)
|
||||||
|
|
||||||
|
assert response.status_code == 200, 'Event info gathering failed'
|
||||||
|
|
||||||
|
response = client.get('/', headers=headers)
|
||||||
|
response_events = response.json()
|
||||||
|
|
||||||
|
assert response.status_code == 200, 'Event info getting failed'
|
||||||
|
assert response_events
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app.api.items.routers import items_router
|
||||||
|
|
||||||
|
client = TestClient(items_router)
|
||||||
|
|
||||||
|
|
||||||
|
def test_items() -> None:
|
||||||
|
# create_transaction = auth_client.post('/token', json=telegram_init_data)
|
||||||
|
# assert auth_response.status_code == 200, 'Failed to authenticate'
|
||||||
|
#
|
||||||
|
# token = auth_response.json().get('access_token')
|
||||||
|
# assert token is not None, 'Token not found in response'
|
||||||
|
|
||||||
|
# headers = {'Authorization': f'Bearer {token}'}
|
||||||
|
|
||||||
|
event_data = [
|
||||||
|
{
|
||||||
|
'title': 'vodka',
|
||||||
|
'price': 1000,
|
||||||
|
'all_users_selected': False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
assert event_data
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import fastapi.testclient
|
||||||
|
|
||||||
|
import app.api.auth.routers
|
||||||
|
|
||||||
|
auth_client = fastapi.testclient.TestClient(app.api.auth.routers.auth_router)
|
||||||
|
|
||||||
|
telegram_init_data = {
|
||||||
|
'query_id': '1',
|
||||||
|
'user': {
|
||||||
|
'id': 0,
|
||||||
|
'first_name': 'string',
|
||||||
|
'last_name': '1',
|
||||||
|
'username': '1',
|
||||||
|
'language_code': '1',
|
||||||
|
'is_premium': False,
|
||||||
|
'added_to_attachment_menu': False,
|
||||||
|
'allows_write_to_pm': False,
|
||||||
|
'photo_url': '1',
|
||||||
|
},
|
||||||
|
'receiver': '1',
|
||||||
|
'chat': '1',
|
||||||
|
'chat_type': '1',
|
||||||
|
'chat_instance': '1',
|
||||||
|
'start_param': '1',
|
||||||
|
'can_send_after': '1',
|
||||||
|
'auth_date': 0,
|
||||||
|
'hash': 'string',
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app.api.transactions.routers import transactions_router
|
||||||
|
|
||||||
|
client = TestClient(transactions_router)
|
||||||
|
|
||||||
|
|
||||||
|
def test_transactions() -> None:
|
||||||
|
# create_transaction = auth_client.post('/token', json=telegram_init_data)
|
||||||
|
# assert auth_response.status_code == 200, 'Failed to authenticate'
|
||||||
|
#
|
||||||
|
# token = auth_response.json().get('access_token')
|
||||||
|
# assert token is not None, 'Token not found in response'
|
||||||
|
|
||||||
|
# headers = {'Authorization': f'Bearer {token}'}
|
||||||
|
|
||||||
|
event_data = [
|
||||||
|
{
|
||||||
|
'title': 'vodka',
|
||||||
|
'price': 1000,
|
||||||
|
'all_users_selected': False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
assert event_data
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app.api.utils.routers import utils_router
|
||||||
|
|
||||||
|
client = TestClient(utils_router)
|
||||||
|
|
||||||
|
|
||||||
|
def test_read_main():
|
||||||
|
response = client.get('/health-check')
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response.json() == {'msg': 'healthy'}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
import sqlmodel
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
from app.models.user import User
|
||||||
|
|
||||||
|
engine = sqlmodel.create_engine(config.DATABASE_URL)
|
||||||
|
|
||||||
|
|
||||||
|
class UserFactory:
|
||||||
|
@staticmethod
|
||||||
|
def new_user(
|
||||||
|
id: int,
|
||||||
|
username: str,
|
||||||
|
events: list['Event'] = None,
|
||||||
|
items: list['Item'] = None,
|
||||||
|
transactions: list['Transaction'] = None,
|
||||||
|
):
|
||||||
|
with sqlmodel.Session(engine) as session:
|
||||||
|
user = User(
|
||||||
|
id=id,
|
||||||
|
username=username,
|
||||||
|
events=events if events else [],
|
||||||
|
items=items if items else [],
|
||||||
|
transactions=transactions if transactions else [],
|
||||||
|
)
|
||||||
|
session.add(user)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bulk_new_users(users: list[dict]):
|
||||||
|
with sqlmodel.Session(engine) as session:
|
||||||
|
print(session.exec(sqlmodel.select(User)).all())
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
UserFactory.new_user(**user)
|
||||||
|
|
||||||
|
|
||||||
|
class EventFactory:
|
||||||
|
@staticmethod
|
||||||
|
def new_event(
|
||||||
|
id: int,
|
||||||
|
owner_id: int,
|
||||||
|
invite: uuid.UUID = None,
|
||||||
|
users: list['User'] = None,
|
||||||
|
transactions: list['Transaction'] = None,
|
||||||
|
):
|
||||||
|
with sqlmodel.Session(engine) as session:
|
||||||
|
user = User(
|
||||||
|
id=id,
|
||||||
|
username=username,
|
||||||
|
events=events,
|
||||||
|
items=items,
|
||||||
|
transactions=transactions,
|
||||||
|
)
|
||||||
|
session.add(user)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bulk_new_users(users: list[dict]):
|
||||||
|
with sqlmodel.Session(engine) as session:
|
||||||
|
print(session.exec(sqlmodel.select(User)).all())
|
||||||
|
|
||||||
|
for user in users:
|
||||||
|
UserFactory.new_user(**user)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
|
|
||||||
|
async def get_nalog_data(ofd_string: str) -> dict:
|
||||||
|
async with ClientSession() as session:
|
||||||
|
url = 'https://proverkacheka.com/api/v1/check/get'
|
||||||
|
data = {'qrraw': ofd_string, 'token': '{{sensitive_data}}'}
|
||||||
|
response = await session.post(
|
||||||
|
url=url,
|
||||||
|
data=data,
|
||||||
|
headers={
|
||||||
|
'Content-Type': (
|
||||||
|
'application/x-www-form-urlencoded;' ' charset=UTF-8'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if response.status != 200:
|
||||||
|
logging.error('Received non-200 status on ofd check')
|
||||||
|
return
|
||||||
|
data = await response.json()
|
||||||
|
if data['code'] == 1:
|
||||||
|
return data
|
||||||
|
logging.error(
|
||||||
|
f'Received non-success status on ofd request with data: {data}'
|
||||||
|
)
|
||||||
|
return
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
|
||||||
|
TELEGRAM_BOT_TOKEN = config.TOKEN_TELEGRAM_API
|
||||||
|
|
||||||
|
|
||||||
|
def send_telegram_message(chat_id: int, text: str):
|
||||||
|
url = f'https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage'
|
||||||
|
payload = {'chat_id': chat_id, 'text': text}
|
||||||
|
response = requests.post(url, json=payload)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
logging.error(f'Failed to send message to {chat_id}: {response.text}')
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
[project]
|
||||||
|
name = "app"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
requires-python = ">=3.10,<4.0"
|
||||||
|
dependencies = [
|
||||||
|
"fastapi[standard]<1.0.0,>=0.114.2",
|
||||||
|
"email-validator<3.0.0.0,>=2.1.0.post1",
|
||||||
|
"passlib[bcrypt]<2.0.0,>=1.7.4",
|
||||||
|
"pydantic>2.0",
|
||||||
|
"jinja2<4.0.0,>=3.1.4",
|
||||||
|
"alembic<2.0.0,>=1.12.1",
|
||||||
|
"httpx<1.0.0,>=0.25.1",
|
||||||
|
"psycopg[binary]<4.0.0,>=3.1.13",
|
||||||
|
"sqlmodel<1.0.0,>=0.0.21",
|
||||||
|
# Pin bcrypt until passlib supports the latest
|
||||||
|
"bcrypt==4.0.1",
|
||||||
|
"pydantic-settings<3.0.0,>=2.2.1",
|
||||||
|
"pyjwt<3.0.0,>=2.8.0",
|
||||||
|
"aiohttp>=3.10.10",
|
||||||
|
"requests>=2.32.3",
|
||||||
|
"pytest-cov>=6.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = [
|
||||||
|
"pytest<8.0.0,>=7.4.3",
|
||||||
|
"mypy<2.0.0,>=1.8.0",
|
||||||
|
"ruff<1.0.0,>=0.2.2",
|
||||||
|
"isort>=5.13.2",
|
||||||
|
"pre-commit<4.0.0,>=3.6.2",
|
||||||
|
"types-passlib<2.0.0.0,>=1.7.7.20240106",
|
||||||
|
"coverage<8.0.0,>=7.4.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
strict = true
|
||||||
|
exclude = ["venv", ".venv", "alembic"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "tests are disabled lol"
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# Run migrations
|
||||||
|
alembic upgrade head
|
||||||
Executable
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
coverage run --source=app -m pytest
|
||||||
|
coverage report --show-missing
|
||||||
|
coverage html --title "${@-coverage}"
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
bash scripts/test.sh "$@"
|
||||||
Generated
+1793
File diff suppressed because it is too large
Load Diff
+164
@@ -0,0 +1,164 @@
|
|||||||
|
__pycache__
|
||||||
|
app.egg-info
|
||||||
|
*.pyc
|
||||||
|
.mypy_cache
|
||||||
|
.coverage
|
||||||
|
htmlcov
|
||||||
|
.cache
|
||||||
|
.venv
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||||
|
.pdm.toml
|
||||||
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
FROM python:3.12
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
|
||||||
|
# Install uv
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
|
||||||
|
COPY --from=ghcr.io/astral-sh/uv:0.4.15 /uv /bin/uv
|
||||||
|
|
||||||
|
# Place executables in the environment at the front of the path
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#using-the-environment
|
||||||
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
|
|
||||||
|
# Compile bytecode
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode
|
||||||
|
ENV UV_COMPILE_BYTECODE=1
|
||||||
|
|
||||||
|
# uv Cache
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#caching
|
||||||
|
ENV UV_LINK_MODE=copy
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
--mount=type=bind,source=uv.lock,target=uv.lock \
|
||||||
|
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
|
||||||
|
uv sync --frozen --no-install-project
|
||||||
|
|
||||||
|
ENV PYTHONPATH=/app
|
||||||
|
|
||||||
|
COPY ./pyproject.toml ./uv.lock /app/
|
||||||
|
|
||||||
|
COPY ./app /app/app
|
||||||
|
|
||||||
|
# Sync the project
|
||||||
|
# Ref: https://docs.astral.sh/uv/guides/integration/docker/#intermediate-layers
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||||
|
uv sync
|
||||||
|
|
||||||
|
CMD ["python", "app/main.py"]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import pydantic
|
||||||
|
import pydantic_settings
|
||||||
|
|
||||||
|
|
||||||
|
class Config(pydantic_settings.BaseSettings):
|
||||||
|
model_config = pydantic_settings.SettingsConfigDict(
|
||||||
|
env_file='../.env',
|
||||||
|
env_ignore_empty=True,
|
||||||
|
extra='ignore',
|
||||||
|
)
|
||||||
|
|
||||||
|
TOKEN_TELEGRAM_API: str = pydantic.fields.Field(default=None)
|
||||||
|
WEB_APP_URL: str = pydantic.fields.Field(default=None)
|
||||||
|
|
||||||
|
|
||||||
|
config = Config()
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import aiogram.types
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
|
||||||
|
inline_web_app_buttons = [
|
||||||
|
[
|
||||||
|
aiogram.types.InlineKeyboardButton(
|
||||||
|
text='Запустить',
|
||||||
|
web_app=aiogram.types.WebAppInfo(url=config.WEB_APP_URL),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
inline_web_app = aiogram.types.InlineKeyboardMarkup(
|
||||||
|
inline_keyboard=inline_web_app_buttons
|
||||||
|
)
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
GREETING = """
|
||||||
|
Добро пожаловать в CoPay!
|
||||||
|
Используйте web-app для управления аккаунтом.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import aiogram.client.default
|
||||||
|
import aiogram.dispatcher.dispatcher
|
||||||
|
|
||||||
|
from app.core.config import config
|
||||||
|
import app.routing.routers
|
||||||
|
|
||||||
|
bot = aiogram.Bot(token=config.TOKEN_TELEGRAM_API)
|
||||||
|
dp = aiogram.dispatcher.dispatcher.Dispatcher()
|
||||||
|
|
||||||
|
dp.include_router(app.routing.routers.routing_router)
|
||||||
|
|
||||||
|
|
||||||
|
async def main() -> None:
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
logging.basicConfig(level=logging.INFO, stream=sys.stdout)
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all handlers
|
||||||
|
from .handlers import *
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
__all__: list[str] = []
|
||||||
|
|
||||||
|
# Initialize all handlers
|
||||||
|
from .handlers import *
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import aiogram.filters
|
||||||
|
import aiogram.types
|
||||||
|
|
||||||
|
import app.core.layouts
|
||||||
|
import app.core.prompts
|
||||||
|
from app.routing.base.routers import base_router
|
||||||
|
|
||||||
|
|
||||||
|
@base_router.message(aiogram.filters.Command('start'))
|
||||||
|
async def start(message: aiogram.types.Message) -> None:
|
||||||
|
await message.reply(
|
||||||
|
app.core.prompts.GREETING, reply_markup=app.core.layouts.inline_web_app
|
||||||
|
)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import aiogram
|
||||||
|
|
||||||
|
base_router = aiogram.Router(name=__package__)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import aiogram
|
||||||
|
|
||||||
|
import app.routing.base.routers
|
||||||
|
|
||||||
|
routing_router = aiogram.Router(name=__package__)
|
||||||
|
|
||||||
|
routing_router.include_routers(
|
||||||
|
app.routing.base.routers.base_router,
|
||||||
|
)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[project]
|
||||||
|
name = "app"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Add your description here"
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
dependencies = [
|
||||||
|
"aiogram>=3.14.0",
|
||||||
|
"pydantic>=2.9.2",
|
||||||
|
"pydantic-settings>=2.6.1",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = [
|
||||||
|
"mypy<2.0.0,>=1.8.0",
|
||||||
|
"ruff<1.0.0,>=0.2.2",
|
||||||
|
"isort>=5.13.2",
|
||||||
|
"coverage<8.0.0,>=7.4.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
Generated
+526
@@ -0,0 +1,526 @@
|
|||||||
|
version = 1
|
||||||
|
requires-python = ">=3.12"
|
||||||
|
resolution-markers = [
|
||||||
|
"python_full_version < '3.13'",
|
||||||
|
"python_full_version >= '3.13'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiofiles"
|
||||||
|
version = "24.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiogram"
|
||||||
|
version = "3.14.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aiofiles" },
|
||||||
|
{ name = "aiohttp" },
|
||||||
|
{ name = "certifi" },
|
||||||
|
{ name = "magic-filter" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/7a/5b/83332340b050631b9f025b195f313dd11c12cec269dc165221e7097fa13e/aiogram-3.14.0.tar.gz", hash = "sha256:68529d7cb8b5cfa862b42c687cb5219eab9e8d589b4729e4806ece50a192d88f", size = 1356231 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/dc/5a9e066c381284e333da01ac97401e05085ae28a3b201c7ea430905a61bc/aiogram-3.14.0-py3-none-any.whl", hash = "sha256:ac86088673b04a30bb005a0075610d0048fcf4e9d1b33e42ab13502410cb1cf6", size = 595400 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiohappyeyeballs"
|
||||||
|
version = "2.4.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bc/69/2f6d5a019bd02e920a3417689a89887b39ad1e350b562f9955693d900c40/aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586", size = 21809 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/d8/120cd0fe3e8530df0539e71ba9683eade12cae103dd7543e50d15f737917/aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572", size = 14742 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiohttp"
|
||||||
|
version = "3.10.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aiohappyeyeballs" },
|
||||||
|
{ name = "aiosignal" },
|
||||||
|
{ name = "attrs" },
|
||||||
|
{ name = "frozenlist" },
|
||||||
|
{ name = "multidict" },
|
||||||
|
{ name = "yarl" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/17/7e/16e57e6cf20eb62481a2f9ce8674328407187950ccc602ad07c685279141/aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a", size = 7542993 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/99/4c5aefe5ad06a1baf206aed6598c7cdcbc7c044c46801cd0d1ecb758cae3/aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c", size = 583536 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/36/8b3bc49b49cb6d2da40ee61ff15dbcc44fd345a3e6ab5bb20844df929821/aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28", size = 395693 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/77/0aa8660dcf11fa65d61712dbb458c4989de220a844bd69778dff25f2d50b/aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f", size = 390898 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/d2/b833d95deb48c75db85bf6646de0a697e7fb5d87bd27cbade4f9746b48b1/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138", size = 1312060 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/5f/29fd5113165a0893de8efedf9b4737e0ba92dfcd791415a528f947d10299/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742", size = 1350553 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/cc/f835f74b7d344428469200105236d44606cfa448be1e7c95ca52880d9bac/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7", size = 1392646 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/fe/1332409d845ca601893bbf2d76935e0b93d41686e5f333841c7d7a4a770d/aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16", size = 1306310 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/a1/25a7633a5a513278a9892e333501e2e69c83e50be4b57a62285fb7a008c3/aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8", size = 1260255 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/39/30eafe89e0e2a06c25e4762844c8214c0c0cd0fd9ffc3471694a7986f421/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6", size = 1271141 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/fc/33125df728b48391ef1fcb512dfb02072158cc10d041414fb79803463020/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a", size = 1280244 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/61/e42bf2c2934b5caa4e2ec0b5e5fd86989adb022b5ee60c2572a9d77cf6fe/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9", size = 1316805 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/32/f52a5e2ae9ad3bba10e026a63a7a23abfa37c7d97aeeb9004eaa98df3ce3/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a", size = 1343930 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/be/6a403b464dcab3631fe8e27b0f1d906d9e45c5e92aca97ee007e5a895560/aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205", size = 1306186 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/fd/bb50fe781068a736a02bf5c7ad5f3ab53e39f1d1e63110da6d30f7605edc/aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628", size = 359289 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/9e/5add7e240f77ef67c275c82cc1d08afbca57b77593118c1f6e920ae8ad3f/aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf", size = 379313 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/eb/618b1b76c7fe8082a71c9d62e3fe84c5b9af6703078caa9ec57850a12080/aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28", size = 576114 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/aa/37/3126995d7869f8b30d05381b81a2d4fb4ec6ad313db788e009bc6d39c211/aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d", size = 391901 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3e/f2/8fdfc845be1f811c31ceb797968523813f8e1263ee3e9120d61253f6848f/aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79", size = 387418 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/60/d5/33d2061d36bf07e80286e04b7e0a4de37ce04b5ebfed72dba67659a05250/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e", size = 1287073 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/52/affb55be16a4747740bd630b4c002dac6c5eac42f9bb64202fc3cf3f1930/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6", size = 1323612 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/f2/cddb69b975387daa2182a8442566971d6410b8a0179bb4540d81c97b1611/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42", size = 1368406 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/e4/afba7327da4d932da8c6e29aecaf855f9d52dace53ac15bfc8030a246f1b/aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e", size = 1282761 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9f/6b/364856faa0c9031ea76e24ef0f7fef79cddd9fa8e7dba9a1771c6acc56b5/aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc", size = 1236518 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/46/af/c382846f8356fe64a7b5908bb9b477457aa23b71be7ed551013b7b7d4d87/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a", size = 1250344 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/87/53/294f87fc086fd0772d0ab82497beb9df67f0f27a8b3dd5742a2656db2bc6/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414", size = 1248956 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/86/30/7d746717fe11bdfefb88bb6c09c5fc985d85c4632da8bb6018e273899254/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3", size = 1293379 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/b9/45d670a834458db67a24258e9139ba61fa3bd7d69b98ecf3650c22806f8f/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67", size = 1320108 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/72/8c/804bb2e837a175635d2000a0659eafc15b2e9d92d3d81c8f69e141ecd0b0/aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b", size = 1281546 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/c0/862e6a9de3d6eeb126cd9d9ea388243b70df9b871ce1a42b193b7a4a77fc/aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8", size = 357516 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/63/3e1aee3e554263f3f1011cca50d78a4894ae16ce99bf78101ac3a2f0ef74/aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151", size = 376785 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiosignal"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "frozenlist" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", size = 19422 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", size = 7617 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "annotated-types"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "app"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = { editable = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "aiogram" },
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "pydantic-settings" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dev-dependencies]
|
||||||
|
dev = [
|
||||||
|
{ name = "coverage" },
|
||||||
|
{ name = "isort" },
|
||||||
|
{ name = "mypy" },
|
||||||
|
{ name = "ruff" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [
|
||||||
|
{ name = "aiogram", specifier = ">=3.14.0" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.9.2" },
|
||||||
|
{ name = "pydantic-settings", specifier = ">=2.6.1" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata.requires-dev]
|
||||||
|
dev = [
|
||||||
|
{ name = "coverage", specifier = ">=7.4.3,<8.0.0" },
|
||||||
|
{ name = "isort", specifier = ">=5.13.2" },
|
||||||
|
{ name = "mypy", specifier = ">=1.8.0,<2.0.0" },
|
||||||
|
{ name = "ruff", specifier = ">=0.2.2,<1.0.0" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "attrs"
|
||||||
|
version = "24.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "certifi"
|
||||||
|
version = "2024.8.30"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coverage"
|
||||||
|
version = "7.6.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/52/12/3669b6382792783e92046730ad3327f53b2726f0603f4c311c4da4824222/coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73", size = 798716 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7d/e7/9291de916d084f41adddfd4b82246e68d61d6a75747f075f7e64628998d2/coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2", size = 207013 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/27/03/932c2c5717a7fa80cd43c6a07d3177076d97b79f12f40f882f9916db0063/coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117", size = 207251 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/3f/0af47dcb9327f65a45455fbca846fe96eb57c153af46c4754a3ba678938a/coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613", size = 240268 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/3c/37a9d81bbd4b23bc7d46ca820e16174c613579c66342faa390a271d2e18b/coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27", size = 237298 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/70/6b0627e5bd68204ee580126ed3513140b2298995c1233bd67404b4e44d0e/coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52", size = 239367 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/eb/634d7dfab24ac3b790bebaf9da0f4a5352cbc125ce6a9d5c6cf4c6cae3c7/coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2", size = 238853 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d9/0d/8e3ed00f1266ef7472a4e33458f42e39492e01a64281084fb3043553d3f1/coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1", size = 237160 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/9c/4337f468ef0ab7a2e0887a9c9da0e58e2eada6fc6cbee637a4acd5dfd8a9/coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5", size = 238824 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/09/3e94912b8dd37251377bb02727a33a67ee96b84bbbe092f132b401ca5dd9/coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17", size = 209639 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/69/d4f3a4101171f32bc5b3caec8ff94c2c60f700107a6aaef7244b2c166793/coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08", size = 210428 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c2/4d/2dede4f7cb5a70fb0bb40a57627fddf1dbdc6b9c1db81f7c4dcdcb19e2f4/coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9", size = 207039 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/f9/d86368ae8c79e28f1fb458ebc76ae9ff3e8bd8069adc24e8f2fed03c58b7/coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba", size = 207298 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/c5/b4cc3c3f64622c58fbfd4d8b9a7a8ce9d355f172f91fcabbba1f026852f6/coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c", size = 239813 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/86/14c42e60b70a79b26099e4d289ccdfefbc68624d096f4481163085aa614c/coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06", size = 236959 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7f/f8/4436a643631a2fbab4b44d54f515028f6099bfb1cd95b13cfbf701e7f2f2/coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f", size = 238950 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/49/50/1571810ddd01f99a0a8be464a4ac8b147f322cd1e8e296a1528984fc560b/coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b", size = 238610 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/8c/6312d241fe7cbd1f0cade34a62fea6f333d1a261255d76b9a87074d8703c/coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21", size = 236697 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ce/5f/fef33dfd05d87ee9030f614c857deb6df6556b8f6a1c51bbbb41e24ee5ac/coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a", size = 238541 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/64/6a984b6e92e1ea1353b7ffa08e27f707a5e29b044622445859200f541e8c/coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e", size = 209707 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5c/60/ce5a9e942e9543783b3db5d942e0578b391c25cdd5e7f342d854ea83d6b7/coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963", size = 210439 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/78/53/6719677e92c308207e7f10561a1b16ab8b5c00e9328efc9af7cfd6fb703e/coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f", size = 207784 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/dd/7054928930671fcb39ae6a83bb71d9ab5f0afb733172543ced4b09a115ca/coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806", size = 208058 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b5/7d/fd656ddc2b38301927b9eb3aae3fe827e7aa82e691923ed43721fd9423c9/coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11", size = 250772 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/90/d0/eb9a3cc2100b83064bb086f18aedde3afffd7de6ead28f69736c00b7f302/coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3", size = 246490 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/44/3f64f38f6faab8a0cfd2c6bc6eb4c6daead246b97cf5f8fc23bf3788f841/coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a", size = 248848 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/11/4c465a5f98656821e499f4b4619929bd5a34639c466021740ecdca42aa30/coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc", size = 248340 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f1/96/ebecda2d016cce9da812f404f720ca5df83c6b29f65dc80d2000d0078741/coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70", size = 246229 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/d9/3d820c00066ae55d69e6d0eae11d6149a5ca7546de469ba9d597f01bf2d7/coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef", size = 247510 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8f/c3/4fa1eb412bb288ff6bfcc163c11700ff06e02c5fad8513817186e460ed43/coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e", size = 210353 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/77/03fc2979d1538884d921c2013075917fc927f41cd8526909852fe4494112/coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1", size = 211502 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "frozenlist"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "3.10"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "isort"
|
||||||
|
version = "5.13.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "magic-filter"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e6/08/da7c2cc7398cc0376e8da599d6330a437c01d3eace2f2365f300e0f3f758/magic_filter-1.0.12.tar.gz", hash = "sha256:4751d0b579a5045d1dc250625c4c508c18c3def5ea6afaf3957cb4530d03f7f9", size = 11071 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cc/75/f620449f0056eff0ec7c1b1e088f71068eb4e47a46eb54f6c065c6ad7675/magic_filter-1.0.12-py3-none-any.whl", hash = "sha256:e5929e544f310c2b1f154318db8c5cdf544dd658efa998172acd2e4ba0f6c6a6", size = 11335 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "multidict"
|
||||||
|
version = "6.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy"
|
||||||
|
version = "1.13.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "mypy-extensions" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mypy-extensions"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "propcache"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a9/4d/5e5a60b78dbc1d464f8a7bbaeb30957257afdc8512cbb9dfd5659304f5cd/propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70", size = 40951 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/46/a41ca1097769fc548fc9216ec4c1471b772cc39720eb47ed7e38ef0006a9/propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2", size = 80800 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/75/4f/93df46aab9cc473498ff56be39b5f6ee1e33529223d7a4d8c0a6101a9ba2/propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7", size = 46443 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0b/17/308acc6aee65d0f9a8375e36c4807ac6605d1f38074b1581bd4042b9fb37/propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8", size = 45676 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/65/44/626599d2854d6c1d4530b9a05e7ff2ee22b790358334b475ed7c89f7d625/propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793", size = 246191 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f2/df/5d996d7cb18df076debae7d76ac3da085c0575a9f2be6b1f707fe227b54c/propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09", size = 251791 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/6d/9f91e5dde8b1f662f6dd4dff36098ed22a1ef4e08e1316f05f4758f1576c/propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89", size = 253434 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3c/e9/1b54b7e26f50b3e0497cd13d3483d781d284452c2c50dd2a615a92a087a3/propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e", size = 248150 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a7/ef/a35bf191c8038fe3ce9a414b907371c81d102384eda5dbafe6f4dce0cf9b/propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9", size = 233568 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/d9/d00bb9277a9165a5e6d60f2142cd1a38a750045c9c12e47ae087f686d781/propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4", size = 229874 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8e/78/c123cf22469bdc4b18efb78893e69c70a8b16de88e6160b69ca6bdd88b5d/propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c", size = 225857 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/31/1b/fd6b2f1f36d028820d35475be78859d8c89c8f091ad30e377ac49fd66359/propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887", size = 227604 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/99/36/b07be976edf77a07233ba712e53262937625af02154353171716894a86a6/propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57", size = 238430 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0d/64/5822f496c9010e3966e934a011ac08cac8734561842bc7c1f65586e0683c/propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23", size = 244814 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fd/bd/8657918a35d50b18a9e4d78a5df7b6c82a637a311ab20851eef4326305c1/propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348", size = 235922 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/6f/ec0095e1647b4727db945213a9f395b1103c442ef65e54c62e92a72a3f75/propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5", size = 40177 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/a2/bd0896fdc4f4c1db46d9bc361c8c79a9bf08ccc08ba054a98e38e7ba1557/propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3", size = 44446 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/a7/5f37b69197d4f558bfef5b4bceaff7c43cc9b51adf5bd75e9081d7ea80e4/propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7", size = 78120 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/cd/48ab2b30a6b353ecb95a244915f85756d74f815862eb2ecc7a518d565b48/propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763", size = 45127 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/ba/0a1ef94a3412aab057bd996ed5f0ac7458be5bf469e85c70fa9ceb43290b/propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d", size = 44419 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/6c/ca70bee4f22fa99eacd04f4d2f1699be9d13538ccf22b3169a61c60a27fa/propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a", size = 229611 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/70/47b872a263e8511ca33718d96a10c17d3c853aefadeb86dc26e8421184b9/propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b", size = 234005 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4f/be/3b0ab8c84a22e4a3224719099c1229ddfdd8a6a1558cf75cb55ee1e35c25/propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb", size = 237270 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/d8/f071bb000d4b8f851d312c3c75701e586b3f643fe14a2e3409b1b9ab3936/propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf", size = 231877 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/93/e7/57a035a1359e542bbb0a7df95aad6b9871ebee6dce2840cb157a415bd1f3/propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2", size = 217848 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f0/93/d1dea40f112ec183398fb6c42fde340edd7bab202411c4aa1a8289f461b6/propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f", size = 216987 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/4c/877340871251145d3522c2b5d25c16a1690ad655fbab7bb9ece6b117e39f/propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136", size = 212451 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/bb/a91b72efeeb42906ef58ccf0cdb87947b54d7475fee3c93425d732f16a61/propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325", size = 212879 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9b/7f/ee7fea8faac57b3ec5d91ff47470c6c5d40d7f15d0b1fccac806348fa59e/propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44", size = 222288 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ff/d7/acd67901c43d2e6b20a7a973d9d5fd543c6e277af29b1eb0e1f7bd7ca7d2/propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83", size = 228257 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8d/6f/6272ecc7a8daad1d0754cfc6c8846076a8cb13f810005c79b15ce0ef0cf2/propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544", size = 221075 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/bd/c7a6a719a6b3dd8b3aeadb3675b5783983529e4a3185946aa444d3e078f6/propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032", size = 39654 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/e7/0eef39eff84fa3e001b44de0bd41c7c0e3432e7648ffd3d64955910f002d/propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e", size = 43705 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/b6/e6d98278f2d49b22b4d033c9f792eda783b9ab2094b041f013fc69bcde87/propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036", size = 11603 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic"
|
||||||
|
version = "2.9.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "annotated-types" },
|
||||||
|
{ name = "pydantic-core" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-core"
|
||||||
|
version = "2.23.4"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pydantic-settings"
|
||||||
|
version = "2.6.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "pydantic" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b5/d4/9dfbe238f45ad8b168f5c96ee49a3df0598ce18a0795a983b419949ce65b/pydantic_settings-2.6.1.tar.gz", hash = "sha256:e0f92546d8a9923cb8941689abf85d6601a8c19a23e97a34b2964a2e3f813ca0", size = 75646 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/f9/ff95fd7d760af42f647ea87f9b8a383d891cdb5e5dbd4613edaeb094252a/pydantic_settings-2.6.1-py3-none-any.whl", hash = "sha256:7fb0637c786a558d3103436278a7c4f1cfd29ba8973238a50c5bb9a55387da87", size = 28595 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/4b/06/09d1276df977eece383d0ed66052fc24ec4550a61f8fbc0a11200e690496/ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313", size = 3243664 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c0/56/933d433c2489e4642487b835f53dd9ff015fb3d8fa459b09bb2ce42d7c4b/ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344", size = 10372090 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/ea/1f0a22a6bcdd3fc26c73f63a025d05bd565901b729d56bcb093c722a6c4c/ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0", size = 10190037 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/16/74/aca75666e0d481fe394e76a8647c44ea919087748024924baa1a17371e3e/ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9", size = 9811998 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/20/a1/cf446a0d7f78ea1f0bd2b9171c11dfe746585c0c4a734b25966121eb4f5d/ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5", size = 10620626 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/cd/c1/82b27d09286ae855f5d03b1ad37cf243f21eb0081732d4d7b0d658d439cb/ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299", size = 10177598 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b9/42/c0acac22753bf74013d035a5ef6c5c4c40ad4d6686bfb3fda7c6f37d9b37/ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e", size = 11171963 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/18/bb0befb7fb9121dd9009e6a72eb98e24f1bacb07c6f3ecb55f032ba98aed/ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29", size = 11856157 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5e/91/04e98d7d6e32eca9d1372be595f9abc7b7f048795e32eb2edbd8794d50bd/ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5", size = 11440331 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f5/dc/3fe99f2ce10b76d389041a1b9f99e7066332e479435d4bebcceea16caff5/ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67", size = 12725354 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/43/7b/1daa712de1c5bc6cbbf9fa60e9c41cc48cda962dc6d2c4f2a224d2c3007e/ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2", size = 11010091 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b6/db/1227a903587432eb569e57a95b15a4f191a71fe315cde4c0312df7bc85da/ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d", size = 10610687 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/db/e2/dc41ee90c3085aadad4da614d310d834f641aaafddf3dfbba08210c616ce/ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2", size = 10254843 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/09/5f6cac1c91542bc5bd33d40b4c13b637bf64d7bb29e091dadb01b62527fe/ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2", size = 10730962 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d3/42/89a4b9a24ef7d00269e24086c417a006f9a3ffeac2c80f2629eb5ce140ee/ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16", size = 11101907 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/5c/efdb4777686683a8edce94ffd812783bddcd3d2454d38c5ac193fef7c500/ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc", size = 8611095 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/b8/28fbc6a4efa50178f973972d1c84b2d0a33cdc731588522ab751ac3da2f5/ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088", size = 9418283 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typing-extensions"
|
||||||
|
version = "4.12.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yarl"
|
||||||
|
version = "1.17.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "idna" },
|
||||||
|
{ name = "multidict" },
|
||||||
|
{ name = "propcache" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/54/9c/9c0a9bfa683fc1be7fdcd9687635151544d992cccd48892dc5e0a5885a29/yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47", size = 178163 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/af/e25615c7920396219b943b9ff8b34636ae3e1ad30777649371317d7f05f8/yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61", size = 141839 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/5e/363d9de3495c7c66592523f05d21576a811015579e0c87dd38c7b5788afd/yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d", size = 94125 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e3/a2/b65447626227ebe36f18f63ac551790068bf42c69bb22dfa3ae986170728/yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139", size = 92048 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/f5/2ef86458446f85cde10582054fd5113495ef8ce8477da35aaaf26d2970ef/yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5", size = 331472 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/6b/1ba79758ba352cdf2ad4c20cab1b982dd369aa595bb0d7601fc89bf82bee/yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac", size = 341260 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/41/4e07c2afca3f9ed3da5b0e38d43d0280d9b624a3d5c478c425e5ce17775c/yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463", size = 340882 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/c0/cd8e94618983c1b811af082e1a7ad7764edb3a6af2bc6b468e0e686238ba/yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147", size = 336648 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ac/fc/73ec4340d391ffbb8f34eb4c55429784ec9f5bd37973ce86d52d67135418/yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7", size = 325019 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/57/48/da3ebf418fc239d0a156b3bdec6b17a5446f8d2dea752299c6e47b143a85/yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685", size = 342841 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5d/79/107272745a470a8167924e353a5312eb52b5a9bb58e22686adc46c94f7ec/yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172", size = 341433 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/30/9c/6459668b3b8dcc11cd061fc53e12737e740fb6b1575b49c84cbffb387b3a/yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7", size = 344927 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c5/0b/93a17ed733aca8164fc3a01cb7d47b3f08854ce4f957cce67a6afdb388a0/yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da", size = 355732 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9a/63/ead2ed6aec3c59397e135cadc66572330325a0c24cd353cd5c94f5e63463/yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c", size = 362123 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/89/bf/f6b75b4c2fcf0e7bb56edc0ed74e33f37fac45dc40e5a52a3be66b02587a/yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199", size = 356355 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/45/1f/50a0257cd07eef65c8c65ad6a21f5fb230012d659e021aeb6ac8a7897bf6/yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96", size = 83279 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bc/82/fafb2c1268d63d54ec08b3a254fbe51f4ef098211501df646026717abee3/yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df", size = 89590 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/1e/5a93e3743c20eefbc68bd89334d9c9f04f3f2334380f7bbf5e950f29511b/yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488", size = 139974 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/be/4e0f6919013c7c5eaea5c31811c551ccd599d2fc80aa3dd6962f1bbdcddd/yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374", size = 93364 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/f0/650f994bc491d0cb85df8bb45392780b90eab1e175f103a5edc61445ff67/yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac", size = 91177 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/e8/9945ed555d14b43ede3ae8b1bd73e31068a694cad2b9d3cad0a28486c2eb/yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170", size = 333086 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a6/c0/7d167e48e14d26639ca066825af8da7df1d2fcdba827e3fd6341aaf22a3b/yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8", size = 343661 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/fa/81/80a266517531d4e3553aecd141800dbf48d02e23ebd52909e63598a80134/yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938", size = 345196 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/77/6adc482ba7f2dc6c0d9b3b492e7cd100edfac4cfc3849c7ffa26fd7beb1a/yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e", size = 338743 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/cc/f0c4c0b92ff3ada517ffde2b127406c001504b225692216d969879ada89a/yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556", size = 326719 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/3b/7bfc80d3376b5fa162189993a87a5a6a58057f88315bd0ea00610055b57a/yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67", size = 345826 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2e/66/cf0b0338107a5c370205c1a572432af08f36ca12ecce127f5b558398b4fd/yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8", size = 340335 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2f/52/b084b0eec0fd4d2490e1d33ace3320fad704c5f1f3deaa709f929d2d87fc/yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3", size = 345301 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ef/38/9e2036d948efd3bafcdb4976cb212166fded76615f0dfc6c1492c4ce4784/yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0", size = 354205 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/81/c1/13dfe1e70b86811733316221c696580725ceb1c46d4e4db852807e134310/yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299", size = 360501 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/91/87/756e05c74cd8bf9e71537df4a2cae7e8211a9ebe0d2350a3e26949e1e41c/yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258", size = 359452 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/b2/b2bb09c1e6d59e1c9b1b36a86caa473e22c3dbf26d1032c030e9bfb554dc/yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2", size = 308904 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/27/f084d9a5668853c1f3b246620269b14ee871ef3c3cc4f3a1dd53645b68ec/yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda", size = 314637 },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/52/ad/1fe7ff5f3e8869d4c5070f47b96bac2b4d15e67c100a8278d8e7876329fc/yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06", size = 44352 },
|
||||||
|
]
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
name: hack
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:12
|
||||||
|
restart: always
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
retries: 5
|
||||||
|
start_period: 30s
|
||||||
|
timeout: 10s
|
||||||
|
volumes:
|
||||||
|
- app-db-data:/var/lib/postgresql/data/pgdata
|
||||||
|
env_file:
|
||||||
|
- path: .env.template
|
||||||
|
required: true
|
||||||
|
- path: .env
|
||||||
|
required: false
|
||||||
|
environment:
|
||||||
|
PGDATA: /var/lib/postgresql/data/pgdata
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
environment:
|
||||||
|
- ADMINER_DESIGN=pepa-linha-dark
|
||||||
|
|
||||||
|
prestart:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
|
command: bash scripts/prestart.sh
|
||||||
|
env_file:
|
||||||
|
- path: .env.template
|
||||||
|
required: true
|
||||||
|
- path: .env
|
||||||
|
required: false
|
||||||
|
environment:
|
||||||
|
- ENVIRONMENT=production
|
||||||
|
- DATABASE_URL=postgresql+psycopg://postgres:postgres@db:5432/postgres
|
||||||
|
|
||||||
|
bot:
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
|
env_file:
|
||||||
|
- path: .env.template
|
||||||
|
required: true
|
||||||
|
- path: .env
|
||||||
|
required: false
|
||||||
|
build:
|
||||||
|
context: ./bot
|
||||||
|
|
||||||
|
backend:
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
ports:
|
||||||
|
- 8000:8000
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
|
prestart:
|
||||||
|
condition: service_completed_successfully
|
||||||
|
env_file:
|
||||||
|
- path: .env.template
|
||||||
|
required: true
|
||||||
|
- path: .env
|
||||||
|
required: false
|
||||||
|
environment:
|
||||||
|
- ENVIRONMENT=production
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8000/api/utils/health-check"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
|
||||||
|
optimizetka:
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
ports:
|
||||||
|
- 3000:8000
|
||||||
|
build:
|
||||||
|
context: ./optimizetka
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
restart: always
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
ports:
|
||||||
|
- 8081:80
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
args:
|
||||||
|
- NODE_ENV=production
|
||||||
|
env_file:
|
||||||
|
- path: .env.template
|
||||||
|
required: true
|
||||||
|
- path: .env
|
||||||
|
required: false
|
||||||
|
- path: ./frontend/.env
|
||||||
|
required: false
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app-db-data:
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user