Compare commits
No commits in common. "807dbee647b7ab7b8d6619b23de3c93210ffa3f3" and "84446d44cecfa6d39253e0fb019ed3150c8adb7e" have entirely different histories.
807dbee647
...
84446d44ce
15
.env.example
15
.env.example
@ -1,22 +1,7 @@
|
||||
# Переменные для базы данных
|
||||
DB_NAME=db_name
|
||||
DB_USER=db_user
|
||||
DB_PASSWORD=db_password
|
||||
DB_HOST=localhost
|
||||
DB_PORT=5432
|
||||
DB_ECHO=True
|
||||
|
||||
# Системные переменные
|
||||
SECRET_KEY=1234567890abcdefghigklmnopqrstuvwxyz
|
||||
FRONTEND_URL=http://127.0.0.1:8000/api/v1
|
||||
|
||||
# Переменные для почты
|
||||
EMAIL_HOST=smtp.yandex.ru
|
||||
EMAIL_PORT=465
|
||||
EMAIL_USERNAME=info@yandex.ru
|
||||
EMAIL_PASSWORD=12345
|
||||
|
||||
# Переменные для Redis
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_DB=0
|
||||
|
11
README.md
11
README.md
@ -21,15 +21,6 @@ PostgreSQL, Poetry, Pydantic и других.
|
||||
- **uvicorn** — высокопроизводительный ASGI-сервер для обработки HTTP-запросов.
|
||||
- **pydantic-settings** — библиотека для работы с конфигурациями и переменными окружения с использованием Pydantic.
|
||||
- **passlib** — библиотека для безопасного хеширования паролей и других данных.
|
||||
- **celery** — распределённая система для выполнения фоновых задач и управления очередями, позволяющая выполнять задачи
|
||||
асинхронно.
|
||||
- **redis** — высокопроизводительное in-memory хранилище, используемое для кэширования данных и как брокер сообщений для
|
||||
Celery.
|
||||
- **itsdangerous** — библиотека для безопасного создания и проверки подписанных данных, что помогает защитить токены и
|
||||
другую чувствительную информацию.
|
||||
- **smtplib** — стандартный модуль Python для отправки электронной почты через протокол SMTP.
|
||||
- **jinja2** — современный и гибкий шаблонизатор, который позволяет динамически генерировать HTML и другие текстовые
|
||||
форматы.
|
||||
|
||||
## Репозитории
|
||||
|
||||
@ -46,8 +37,6 @@ PostgreSQL, Poetry, Pydantic и других.
|
||||
3. [FastAPI 3. Подключение к SQLAlchemy и генератор сессий](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-3-podklyuchenie-k-sqlalchemy-i-generator-s/)
|
||||
4. [FastAPI 4. Модель пользователя, миксины и Alembic](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-4-model-polzovatelya-i-alembic/)
|
||||
5. [FastAPI 5. Приложение аутентификации и Pydantic схемы](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-5-prilozhenie-autentifikacii-i-pydantic-sh/)
|
||||
6. [FastAPI 6. Пользовательский сервис и маршруты регистрации](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-6-polzovatelskij-servis-i-marshruty-regist/)
|
||||
7. [FastAPI 7. Электронная почта, подтверждение регистрации, Celery и Redis](https://pressanybutton.ru/post/servis-na-fastapi/fastapi-7-elektronnaya-pochta-podtverzhdenie-registracii-celery-i-redis/)
|
||||
|
||||
## Установка
|
||||
|
||||
|
@ -7,7 +7,7 @@ https://pressanybutton.ru/category/servis-na-fastapi/
|
||||
"""
|
||||
|
||||
from fastapi import Depends, HTTPException
|
||||
from sqlalchemy import insert, update
|
||||
from sqlalchemy import insert
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
from lkeep.apps.auth.schemas import CreateUser, UserReturnData
|
||||
@ -24,6 +24,8 @@ class UserManager:
|
||||
"""
|
||||
Инициализирует экземпляр класса.
|
||||
|
||||
:param model: Модель, используемая для работы с данными.
|
||||
:type model: Type[User]
|
||||
:param db: Зависимость от базы данных. По умолчанию используется Depends(DBDependency).
|
||||
:type db: DBDependency
|
||||
"""
|
||||
@ -40,7 +42,7 @@ class UserManager:
|
||||
:rtype: UserReturnData
|
||||
:raises HTTPException: Если пользователь уже существует.
|
||||
"""
|
||||
async with self.db.db_session() as session:
|
||||
async with self.db.db_session as session:
|
||||
query = insert(self.model).values(**user.model_dump()).returning(self.model)
|
||||
|
||||
try:
|
||||
@ -50,17 +52,5 @@ class UserManager:
|
||||
|
||||
await session.commit()
|
||||
|
||||
user_data = result.scalar_one()
|
||||
user_data = await result.scalar_one()
|
||||
return UserReturnData(**user_data.__dict__)
|
||||
|
||||
async def confirm_user(self, email: str) -> None:
|
||||
"""
|
||||
Асинхронный метод для подтверждения пользователя по электронной почте.
|
||||
|
||||
:param email: Электронная почта пользователя, которого нужно подтвердить.
|
||||
:type email: str
|
||||
"""
|
||||
async with self.db.db_session() as session:
|
||||
query = update(self.model).where(self.model.email == email).values(is_verified=True, is_active=True)
|
||||
await session.execute(query)
|
||||
await session.commit()
|
||||
|
@ -29,18 +29,3 @@ async def registration(user: RegisterUser, service: UserService = Depends(UserSe
|
||||
:raises HTTPException 400: Если данные пользователя некорректны.
|
||||
"""
|
||||
return await service.register_user(user=user)
|
||||
|
||||
|
||||
@auth_router.get(path="/register_confirm", status_code=status.HTTP_200_OK)
|
||||
async def confirm_registration(token: str, service: UserService = Depends(UserService)) -> dict[str, str]:
|
||||
"""
|
||||
Подтверждает регистрацию пользователя по ссылке.
|
||||
|
||||
:param token: Токен подтверждения регистрации, полученный после отправки на электронную почту.
|
||||
:type token: str
|
||||
:raises HTTPException: Если токен недействителен или срок действия истек.
|
||||
:return: Словарь с сообщением о успешной подтверждении электронной почты.
|
||||
:rtype: dict[str, str]
|
||||
"""
|
||||
await service.confirm_user(token=token)
|
||||
return {"message": "Электронная почта подтверждена"}
|
||||
|
@ -6,14 +6,11 @@
|
||||
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||
"""
|
||||
|
||||
from fastapi import Depends, HTTPException
|
||||
from itsdangerous import BadSignature, URLSafeTimedSerializer
|
||||
from fastapi import Depends
|
||||
|
||||
from lkeep.apps.auth.handlers import AuthHandler
|
||||
from lkeep.apps.auth.managers import UserManager
|
||||
from lkeep.apps.auth.schemas import CreateUser, RegisterUser, UserReturnData
|
||||
from lkeep.apps.auth.tasks import send_confirmation_email
|
||||
from lkeep.core.settings import settings
|
||||
|
||||
|
||||
class UserService:
|
||||
@ -34,39 +31,18 @@ class UserService:
|
||||
"""
|
||||
self.manager = manager
|
||||
self.handler = handler
|
||||
self.serializer = URLSafeTimedSerializer(secret_key=settings.secret_key.get_secret_value())
|
||||
|
||||
async def register_user(self, user: RegisterUser) -> UserReturnData:
|
||||
"""
|
||||
Регистрирует нового пользователя в системе.
|
||||
|
||||
:param user: Информация о пользователе, который нужно зарегистрировать.
|
||||
:param user: Данные для регистрации пользователя.
|
||||
:type user: RegisterUser
|
||||
:returns: Данные о созданном пользователе.
|
||||
:return: Данные зарегистрированного пользователя.
|
||||
:rtype: UserReturnData
|
||||
"""
|
||||
hashed_password = await self.handler.get_password_hash(user.password)
|
||||
|
||||
new_user = CreateUser(email=user.email, hashed_password=hashed_password)
|
||||
|
||||
user_data = await self.manager.create_user(user=new_user)
|
||||
|
||||
confirmation_token = self.serializer.dumps(user_data.email)
|
||||
send_confirmation_email.delay(to_email=user_data.email, token=confirmation_token)
|
||||
|
||||
return user_data
|
||||
|
||||
async def confirm_user(self, token: str) -> None:
|
||||
"""
|
||||
Подтверждает пользователя по переданному токену.
|
||||
|
||||
:param token: Токен для подтверждения пользователя.
|
||||
:type token: str
|
||||
:raises HTTPException: Если токен неверный или просроченный.
|
||||
"""
|
||||
try:
|
||||
email = self.serializer.loads(token, max_age=3600)
|
||||
except BadSignature:
|
||||
raise HTTPException(status_code=400, detail="Неверный или просроченный токен")
|
||||
|
||||
await self.manager.confirm_user(email=email)
|
||||
return await self.manager.create_user(user=new_user)
|
||||
|
@ -1,75 +0,0 @@
|
||||
"""
|
||||
Проект: Lkeep
|
||||
Автор: Иван Ашихмин
|
||||
Год: 2025
|
||||
Специально для проекта "Код на салфетке"
|
||||
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||
"""
|
||||
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
|
||||
from celery import shared_task
|
||||
from starlette.templating import Jinja2Templates
|
||||
|
||||
from lkeep.core.settings import settings
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_text_confirmation_email(to_email: str, token: str) -> None:
|
||||
"""
|
||||
Отправляет текстовое подтверждение регистрации по электронной почте.
|
||||
|
||||
:param to_email: Адрес электронной почты получателя подтверждения.
|
||||
:type to_email: str
|
||||
:param token: Токен для подтверждения регистрации.
|
||||
:type token: str
|
||||
"""
|
||||
confirmation_url = f"{settings.frontend_url}/auth/register_confirm?token={token}"
|
||||
|
||||
text = f"""Спасибо за регистрацию!
|
||||
Для подтверждения регистрации перейдите по ссылке: {confirmation_url}
|
||||
"""
|
||||
|
||||
message = EmailMessage()
|
||||
message.set_content(text)
|
||||
message["From"] = settings.email_settings.email_username
|
||||
message["To"] = to_email
|
||||
message["Subject"] = "Подтверждение регистрации"
|
||||
|
||||
with smtplib.SMTP_SSL(host=settings.email_settings.email_host, port=settings.email_settings.email_port) as smtp:
|
||||
smtp.login(
|
||||
user=settings.email_settings.email_username,
|
||||
password=settings.email_settings.email_password.get_secret_value(),
|
||||
)
|
||||
smtp.send_message(msg=message)
|
||||
|
||||
|
||||
@shared_task
|
||||
def send_confirmation_email(to_email: str, token: str) -> None:
|
||||
"""
|
||||
Отправляет подтверждение регистрации по электронной почте.
|
||||
|
||||
:param to_email: Адрес электронной почты получателя сообщения.
|
||||
:type to_email: str
|
||||
:param token: Токен для подтверждения регистрации, передаваемый в URL.
|
||||
:type token: str
|
||||
"""
|
||||
confirmation_url = f"{settings.frontend_url}/auth/register_confirm?token={token}"
|
||||
|
||||
templates = Jinja2Templates(directory=settings.templates_dir)
|
||||
template = templates.get_template(name="confirmation_email.html")
|
||||
html_content = template.render(confirmation_url=confirmation_url)
|
||||
|
||||
message = EmailMessage()
|
||||
message.add_alternative(html_content, subtype="html")
|
||||
message["From"] = settings.email_settings.email_username
|
||||
message["To"] = to_email
|
||||
message["Subject"] = "Подтверждение регистрации"
|
||||
|
||||
with smtplib.SMTP_SSL(host=settings.email_settings.email_host, port=settings.email_settings.email_port) as smtp:
|
||||
smtp.login(
|
||||
user=settings.email_settings.email_username,
|
||||
password=settings.email_settings.email_password.get_secret_value(),
|
||||
)
|
||||
smtp.send_message(msg=message)
|
@ -5,7 +5,3 @@
|
||||
Специально для проекта "Код на салфетке"
|
||||
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||
"""
|
||||
|
||||
from .celery_config import celery_app
|
||||
|
||||
__all__ = ["celery_app"]
|
||||
|
@ -1,15 +0,0 @@
|
||||
"""
|
||||
Проект: Lkeep
|
||||
Автор: Иван Ашихмин
|
||||
Год: 2025
|
||||
Специально для проекта "Код на салфетке"
|
||||
https://pressanybutton.ru/category/servis-na-fastapi/
|
||||
"""
|
||||
|
||||
from celery import Celery
|
||||
|
||||
from lkeep.core.settings import settings
|
||||
|
||||
celery_app = Celery(main="lkeep", broker=settings.redis_settings.redis_url, backend=settings.redis_settings.redis_url)
|
||||
|
||||
celery_app.autodiscover_tasks(packages=["lkeep.apps"])
|
@ -48,83 +48,18 @@ class DBSettings(BaseSettings):
|
||||
return f"postgresql+asyncpg://{self.db_user}:{self.db_password.get_secret_value()}@{self.db_host}:{self.db_port}/{self.db_name}"
|
||||
|
||||
|
||||
class EmailSettings(BaseSettings):
|
||||
"""
|
||||
Настройки для электронной почты.
|
||||
|
||||
:ivar email_host: Адрес SMTP-сервера.
|
||||
:type email_host: str
|
||||
:ivar email_port: Порт, используемый для подключения к SMTP-серверу.
|
||||
:type email_port: int
|
||||
:ivar email_username: Имя пользователя для аутентификации на электронной почтовом сервере.
|
||||
:type email_username: str
|
||||
:ivar email_password: Пароль пользователя, скрытый через `SecretStr` для обеспечения безопасности.
|
||||
:type email_password: SecretStr
|
||||
:model_config: Конфигурация settings, которая указывает на файл окружения и его кодировку.
|
||||
:type model_config: SettingsConfigDict
|
||||
"""
|
||||
|
||||
email_host: str
|
||||
email_port: int
|
||||
email_username: str
|
||||
email_password: SecretStr
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
|
||||
|
||||
|
||||
class RedisSettings(BaseSettings):
|
||||
"""
|
||||
Класс для настройки соединения с Redis.
|
||||
|
||||
:ivar redis_host: Хост, на котором размещается Redis-сервер.
|
||||
:type redis_host: str
|
||||
:ivar redis_port: Порт, через который происходит соединение с Redis-сервером.
|
||||
:type redis_port: int
|
||||
:ivar redis_db: Номер базы данных для использования в Redis.
|
||||
:type redis_db: int
|
||||
"""
|
||||
|
||||
redis_host: str
|
||||
redis_port: int
|
||||
redis_db: int
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
|
||||
|
||||
@property
|
||||
def redis_url(self):
|
||||
"""
|
||||
Получает URL для подключения к Redis.
|
||||
|
||||
:returns: Строка с URL для подключения к Redis в формате `redis://<хост>:<порт>/<база данных>`.
|
||||
:rtype: str
|
||||
"""
|
||||
return f"redis://{self.redis_host}:{self.redis_port}/{self.redis_db}"
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
"""
|
||||
Класс для хранения настроек приложения.
|
||||
Класс Settings используется для хранения настроек приложения.
|
||||
|
||||
:ivar db_settings: Настройки для работы с базой данных.
|
||||
:ivar db_settings: Экземпляр класса DBSettings, содержащий настройки базы данных.
|
||||
:type db_settings: DBSettings
|
||||
:ivar email_settings: Настройки для отправки электронной почты.
|
||||
:type email_settings: EmailSettings
|
||||
:ivar redis_settings: Настройки для работы с Redis.
|
||||
:type redis_settings: RedisSettings
|
||||
:ivar secret_key: Секретный ключ приложения.
|
||||
:ivar secret_key: Секретный ключ для шифрования
|
||||
:type secret_key: SecretStr
|
||||
:ivar templates_dir: Путь к директории шаблонов.
|
||||
:type templates_dir: str
|
||||
:ivar frontend_url: Адрес фронтенд-приложения.
|
||||
:type frontend_url: str
|
||||
"""
|
||||
|
||||
db_settings: DBSettings = DBSettings()
|
||||
email_settings: EmailSettings = EmailSettings()
|
||||
redis_settings: RedisSettings = RedisSettings()
|
||||
secret_key: SecretStr
|
||||
templates_dir: str = "templates"
|
||||
frontend_url: str
|
||||
|
||||
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf8", extra="ignore")
|
||||
|
||||
|
367
poetry.lock
generated
367
poetry.lock
generated
@ -20,21 +20,6 @@ typing-extensions = ">=4"
|
||||
[package.extras]
|
||||
tz = ["backports.zoneinfo"]
|
||||
|
||||
[[package]]
|
||||
name = "amqp"
|
||||
version = "5.3.1"
|
||||
description = "Low-level AMQP client for Python (fork of amqplib)."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "amqp-5.3.1-py3-none-any.whl", hash = "sha256:43b3319e1b4e7d1251833a93d672b4af1e40f3d632d479b98661a95f117880a2"},
|
||||
{file = "amqp-5.3.1.tar.gz", hash = "sha256:cddc00c725449522023bad949f70fff7b48f0b1ade74d170a6f10ab044739432"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
vine = ">=5.0.0,<6.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
@ -168,120 +153,6 @@ files = [
|
||||
tests = ["pytest (>=3.2.1,!=3.3.0)"]
|
||||
typecheck = ["mypy"]
|
||||
|
||||
[[package]]
|
||||
name = "billiard"
|
||||
version = "4.2.1"
|
||||
description = "Python multiprocessing fork with improvements and bugfixes"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "billiard-4.2.1-py3-none-any.whl", hash = "sha256:40b59a4ac8806ba2c2369ea98d876bc6108b051c227baffd928c644d15d8f3cb"},
|
||||
{file = "billiard-4.2.1.tar.gz", hash = "sha256:12b641b0c539073fc8d3f5b8b7be998956665c4233c7c1fcd66a7e677c4fb36f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "25.1.0"
|
||||
description = "The uncompromising code formatter."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"},
|
||||
{file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"},
|
||||
{file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"},
|
||||
{file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"},
|
||||
{file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"},
|
||||
{file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"},
|
||||
{file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"},
|
||||
{file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"},
|
||||
{file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
|
||||
{file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
|
||||
{file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
|
||||
{file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
|
||||
{file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"},
|
||||
{file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"},
|
||||
{file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"},
|
||||
{file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"},
|
||||
{file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"},
|
||||
{file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"},
|
||||
{file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"},
|
||||
{file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"},
|
||||
{file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
|
||||
{file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
mypy-extensions = ">=0.4.3"
|
||||
packaging = ">=22.0"
|
||||
pathspec = ">=0.9.0"
|
||||
platformdirs = ">=2"
|
||||
|
||||
[package.extras]
|
||||
colorama = ["colorama (>=0.4.3)"]
|
||||
d = ["aiohttp (>=3.10)"]
|
||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||
uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "celery"
|
||||
version = "5.4.0"
|
||||
description = "Distributed Task Queue."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "celery-5.4.0-py3-none-any.whl", hash = "sha256:369631eb580cf8c51a82721ec538684994f8277637edde2dfc0dacd73ed97f64"},
|
||||
{file = "celery-5.4.0.tar.gz", hash = "sha256:504a19140e8d3029d5acad88330c541d4c3f64c789d85f94756762d8bca7e706"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
billiard = ">=4.2.0,<5.0"
|
||||
click = ">=8.1.2,<9.0"
|
||||
click-didyoumean = ">=0.3.0"
|
||||
click-plugins = ">=1.1.1"
|
||||
click-repl = ">=0.2.0"
|
||||
kombu = ">=5.3.4,<6.0"
|
||||
python-dateutil = ">=2.8.2"
|
||||
tzdata = ">=2022.7"
|
||||
vine = ">=5.1.0,<6.0"
|
||||
|
||||
[package.extras]
|
||||
arangodb = ["pyArango (>=2.0.2)"]
|
||||
auth = ["cryptography (==42.0.5)"]
|
||||
azureblockblob = ["azure-storage-blob (>=12.15.0)"]
|
||||
brotli = ["brotli (>=1.0.0)", "brotlipy (>=0.7.0)"]
|
||||
cassandra = ["cassandra-driver (>=3.25.0,<4)"]
|
||||
consul = ["python-consul2 (==0.1.5)"]
|
||||
cosmosdbsql = ["pydocumentdb (==2.3.5)"]
|
||||
couchbase = ["couchbase (>=3.0.0)"]
|
||||
couchdb = ["pycouchdb (==1.14.2)"]
|
||||
django = ["Django (>=2.2.28)"]
|
||||
dynamodb = ["boto3 (>=1.26.143)"]
|
||||
elasticsearch = ["elastic-transport (<=8.13.0)", "elasticsearch (<=8.13.0)"]
|
||||
eventlet = ["eventlet (>=0.32.0)"]
|
||||
gcs = ["google-cloud-storage (>=2.10.0)"]
|
||||
gevent = ["gevent (>=1.5.0)"]
|
||||
librabbitmq = ["librabbitmq (>=2.0.0)"]
|
||||
memcache = ["pylibmc (==1.6.3)"]
|
||||
mongodb = ["pymongo[srv] (>=4.0.2)"]
|
||||
msgpack = ["msgpack (==1.0.8)"]
|
||||
pymemcache = ["python-memcached (>=1.61)"]
|
||||
pyro = ["pyro4 (==4.82)"]
|
||||
pytest = ["pytest-celery[all] (>=1.0.0)"]
|
||||
redis = ["redis (>=4.5.2,!=4.5.5,<6.0.0)"]
|
||||
s3 = ["boto3 (>=1.26.143)"]
|
||||
slmq = ["softlayer-messaging (>=1.0.3)"]
|
||||
solar = ["ephem (==4.1.5)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
|
||||
sqs = ["boto3 (>=1.26.143)", "kombu[sqs] (>=5.3.4)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"]
|
||||
tblib = ["tblib (>=1.3.0)", "tblib (>=1.5.0)"]
|
||||
yaml = ["PyYAML (>=3.10)"]
|
||||
zookeeper = ["kazoo (>=1.3.1)"]
|
||||
zstd = ["zstandard (==0.22.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.12.14"
|
||||
@ -312,7 +183,7 @@ version = "8.1.8"
|
||||
description = "Composable command line interface toolkit"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main", "dev"]
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
|
||||
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
|
||||
@ -321,70 +192,18 @@ files = [
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "click-didyoumean"
|
||||
version = "0.3.1"
|
||||
description = "Enables git-like *did-you-mean* feature in click"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click_didyoumean-0.3.1-py3-none-any.whl", hash = "sha256:5c4bb6007cfea5f2fd6583a2fb6701a22a41eb98957e63d0fac41c10e7c3117c"},
|
||||
{file = "click_didyoumean-0.3.1.tar.gz", hash = "sha256:4f82fdff0dbe64ef8ab2279bd6aa3f6a99c3b28c05aa09cbfc07c9d7fbb5a463"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7"
|
||||
|
||||
[[package]]
|
||||
name = "click-plugins"
|
||||
version = "1.1.1"
|
||||
description = "An extension module for click to enable registering CLI commands via setuptools entry-points."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"},
|
||||
{file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=4.0"
|
||||
|
||||
[package.extras]
|
||||
dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "click-repl"
|
||||
version = "0.3.0"
|
||||
description = "REPL plugin for Click"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "click-repl-0.3.0.tar.gz", hash = "sha256:17849c23dba3d667247dc4defe1757fff98694e90fe37474f3feebb69ced26a9"},
|
||||
{file = "click_repl-0.3.0-py3-none-any.whl", hash = "sha256:fb7e06deb8da8de86180a33a9da97ac316751c094c6899382da7feeeeb51b812"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
prompt-toolkit = ">=3.0.36"
|
||||
|
||||
[package.extras]
|
||||
testing = ["pytest (>=7.2.1)", "pytest-cov (>=4.0.0)", "tox (>=4.4.3)"]
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
groups = ["main", "dev"]
|
||||
groups = ["main"]
|
||||
markers = "platform_system == \"Windows\" or sys_platform == \"win32\""
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\"", dev = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
@ -732,18 +551,6 @@ files = [
|
||||
[package.extras]
|
||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
description = "Safely pass data to untrusted environments and back."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"},
|
||||
{file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.5"
|
||||
@ -762,40 +569,6 @@ MarkupSafe = ">=2.0"
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "kombu"
|
||||
version = "5.4.2"
|
||||
description = "Messaging library for Python."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "kombu-5.4.2-py3-none-any.whl", hash = "sha256:14212f5ccf022fc0a70453bb025a1dcc32782a588c49ea866884047d66e14763"},
|
||||
{file = "kombu-5.4.2.tar.gz", hash = "sha256:eef572dd2fd9fc614b37580e3caeafdd5af46c1eff31e7fba89138cdb406f2cf"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
amqp = ">=5.1.1,<6.0.0"
|
||||
tzdata = {version = "*", markers = "python_version >= \"3.9\""}
|
||||
vine = "5.1.0"
|
||||
|
||||
[package.extras]
|
||||
azureservicebus = ["azure-servicebus (>=7.10.0)"]
|
||||
azurestoragequeues = ["azure-identity (>=1.12.0)", "azure-storage-queue (>=12.6.0)"]
|
||||
confluentkafka = ["confluent-kafka (>=2.2.0)"]
|
||||
consul = ["python-consul2 (==0.1.5)"]
|
||||
librabbitmq = ["librabbitmq (>=2.0.0)"]
|
||||
mongodb = ["pymongo (>=4.1.1)"]
|
||||
msgpack = ["msgpack (==1.1.0)"]
|
||||
pyro = ["pyro4 (==4.82)"]
|
||||
qpid = ["qpid-python (>=0.26)", "qpid-tools (>=0.26)"]
|
||||
redis = ["redis (>=4.5.2,!=4.5.5,!=5.0.2)"]
|
||||
slmq = ["softlayer-messaging (>=1.0.3)"]
|
||||
sqlalchemy = ["sqlalchemy (>=1.4.48,<2.1)"]
|
||||
sqs = ["boto3 (>=1.26.143)", "pycurl (>=7.43.0.5)", "urllib3 (>=1.26.16)"]
|
||||
yaml = ["PyYAML (>=3.10)"]
|
||||
zookeeper = ["kazoo (>=2.8.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mako"
|
||||
version = "1.3.8"
|
||||
@ -924,18 +697,6 @@ files = [
|
||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
@ -948,18 +709,6 @@ files = [
|
||||
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
description = "Core utilities for Python packages"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
@ -978,25 +727,13 @@ bcrypt = ["bcrypt (>=3.1.0)"]
|
||||
build-docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
|
||||
totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["dev"]
|
||||
files = [
|
||||
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
|
||||
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main", "dev"]
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
|
||||
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
|
||||
@ -1026,21 +763,6 @@ nodeenv = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
virtualenv = ">=20.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.48"
|
||||
description = "Library for building powerful interactive command lines in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.7.0"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"},
|
||||
{file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
wcwidth = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.5"
|
||||
@ -1212,21 +934,6 @@ files = [
|
||||
[package.extras]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
description = "Extensions to the standard Python datetime module"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
|
||||
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.0.1"
|
||||
@ -1317,22 +1024,6 @@ files = [
|
||||
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "5.2.1"
|
||||
description = "Python client for Redis database and key-value store"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"},
|
||||
{file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
hiredis = ["hiredis (>=3.0.0)"]
|
||||
ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==23.2.1)", "requests (>=2.31.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.9.4"
|
||||
@ -1409,18 +1100,6 @@ files = [
|
||||
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
description = "Python 2 and 3 compatibility utilities"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
|
||||
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
@ -1535,18 +1214,6 @@ files = [
|
||||
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.2"
|
||||
description = "Provider of IANA time zone data"
|
||||
optional = false
|
||||
python-versions = ">=2"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
|
||||
{file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uvicorn"
|
||||
version = "0.34.0"
|
||||
@ -1626,18 +1293,6 @@ dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"]
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||
test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "vine"
|
||||
version = "5.1.0"
|
||||
description = "Python promises."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "vine-5.1.0-py3-none-any.whl", hash = "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc"},
|
||||
{file = "vine-5.1.0.tar.gz", hash = "sha256:8b62e981d35c41049211cf62a0a1242d8c1ee9bd15bb196ce38aefd6799e61e0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.28.1"
|
||||
@ -1743,18 +1398,6 @@ files = [
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
|
||||
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "14.1"
|
||||
@ -1837,4 +1480,4 @@ files = [
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.12"
|
||||
content-hash = "881df1a02c4e0bc5ff26cb0029aef8488bb0391b229e6fd73ce31d45b2baa1fb"
|
||||
content-hash = "3d4c2b89136e414e183f596de6faca17a5c9d78e0b2788ad4cb4fd598e486faa"
|
||||
|
@ -19,9 +19,6 @@ dependencies = [
|
||||
"pydantic[email] (>=2.10.5,<3.0.0)",
|
||||
"passlib (>=1.7.4,<2.0.0)",
|
||||
"bcrypt (==4.0.1)",
|
||||
"celery (>=5.4.0,<6.0.0)",
|
||||
"redis (>=5.2.1,<6.0.0)",
|
||||
"itsdangerous (>=2.2.0,<3.0.0)",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
@ -30,6 +27,3 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[project.scripts]
|
||||
app = "lkeep.main:start"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
black = "^25.1.0"
|
||||
|
@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<title>Подтверждение регистрации</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Подтверждение регистрации</h1>
|
||||
<p>Для подтверждения регистрации перейдите по ссылке:</p>
|
||||
<a href="{{ confirmation_url }}">Подтвердить регистрацию</a>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user