@@ -1,3 +1,14 @@
from authentication.exceptions.business_host_exceptions.base_already import BaseAlready
__all__ = ('BaseAlready',)
+
+
+class InvalidPassword(Exception): ...
+
+
+class InvalidUsername(Exception): ...
+
+
+class InvalidToken(Exception):
+ def __str__(self) -> str:
+ return 'Invalid token received, please try relog'
@@ -0,0 +1,112 @@
+import json
+from datetime import datetime, timezone
+from typing import Any, Literal
+
+import jwt
+from django.conf import settings
+from django.contrib.auth.hashers import check_password
+
+from authentication.exceptions import InvalidPassword, InvalidToken, InvalidUsername
+from authentication.models.user import CustomUserModel
+
+
+class TokenService:
+ @classmethod
+ def issue(cls, *, username: str, password: str) -> dict[str, str]:
+ try:
+ user = CustomUserModel.objects.get(username=username)
+ except CustomUserModel.DoesNotExist:
+ raise InvalidUsername
+ if not check_password(password=password, encoded=user.password):
+ raise InvalidPassword
+
+ user.last_login = datetime.now()
+ user.save()
+
+ payload = {}
+
+ for attrname in settings.JWT_SETTINGS['encode_attributes']:
+ attr = getattr(user, attrname)
+ try:
+ bool(json.dumps({attrname: attr}))
+ except TypeError:
+ attr = str(getattr(user, attrname))
+ finally:
+ payload.update({f'{attrname}': attr})
+ return {
+ 'access': cls._encode(payload=payload, token_type='access'),
+ 'refresh': cls._encode(payload=payload, token_type='refresh'),
+ }
+
+ @classmethod
+ def refresh(cls, *, refresh: str) -> dict[str, str]:
+ decoded_token = cls._decode(token=refresh)
+
+ return {
+ 'access': cls._encode(payload=decoded_token, token_type='access'),
+ }
+
+ @classmethod
+ def force_refresh(cls, *, refresh: str) -> dict[str, str]:
+ decoded_token = cls._decode(token=refresh)
+ user = CustomUserModel.objects.get(
+ **{
+ key: decoded_token[key]
+ for key in decoded_token
+ if key in settings.JWT_SETTINGS['encode_attributes']
+ }
+ )
+
+ payload = {}
+ for attrname in settings.JWT_SETTINGS['encode_attributes']:
+ attr = getattr(user, attrname)
+ try:
+ bool(json.dumps({attrname: attr}))
+ except TypeError:
+ attr = str(getattr(user, attrname))
+ finally:
+ payload.update({attrname: attr})
+
+ return {
+ 'access': cls._encode(payload=payload, token_type='access'),
+ 'refresh': cls._encode(payload=payload, token_type='refresh'),
+ }
+
+ @classmethod
+ async def decode(cls, *, token: str) -> dict[str, Any]:
+ return cls._decode(token=token)
+
+ @classmethod
+ def _encode(
+ cls, *, payload: dict[str, Any], token_type: Literal['access', 'refresh']
+ ) -> str:
+ issued_at = datetime.now(tz=timezone.utc)
+ jwt_signature = {
+ 'exp': issued_at + settings.JWT_SETTINGS[f'{token_type}_expiry_time'],
+ 'iat': issued_at,
+ }
+ jwt_signature.update(payload)
+
+ return jwt.encode(
+ jwt_signature,
+ key=settings.JWT_SETTINGS['secret_key'],
+ algorithm=settings.JWT_SETTINGS['algorithm'],
+ )
+
+ @classmethod
+ def _decode(cls, *, token: str) -> Any:
+ try:
+ return jwt.decode(
+ jwt=token,
+ key=settings.JWT_SETTINGS['secret_key'],
+ algorithms=[settings.JWT_SETTINGS['algorithm']],
+ verify=True,
+ )
+ except (
+ jwt.InvalidAlgorithmError,
+ jwt.InvalidIssuedAtError,
+ jwt.ExpiredSignatureError,
+ jwt.InvalidSignatureError,
+ jwt.DecodeError,
+ ):
+ raise InvalidToken
@@ -0,0 +1,26 @@
+from typing import Any
+
+from asgiref.sync import async_to_sync
+from django.conf import settings
+from django.http import HttpRequest
+from ninja.security import HttpBearer
+from oauth2_provider.models import AccessToken
+
+from authentication.exceptions import InvalidToken
+from authentication.models.user import CustomUserModel
+from authentication.services.token import TokenService
+
+
+class SyncAuthBearer(HttpBearer):
+ def authenticate(self, _: HttpRequest, token: str) -> Any | None:
+ try:
+ user_payload = async_to_sync(TokenService.decode)(token=token)
+ return CustomUserModel.objects.get(
+ **{
+ key: user_payload[f'{key}']
+ for key in settings.JWT_SETTINGS['encode_attributes']
+ }
+ )
+ except InvalidToken:
+ access = AccessToken.objects.prefetch_related('user').get(token=token)
+ return access.user
@@ -0,0 +1,18 @@
+import os
+
+from channels.routing import ProtocolTypeRouter, URLRouter
+from django.core.asgi import get_asgi_application
+from django.urls import path
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
+
+application = get_asgi_application()
+
+from tools.copywrite.router import ws_router as copywrite_ws_router # noqa: E402
+
+application = ProtocolTypeRouter(
+ {
+ 'http': application,
+ 'websocket': URLRouter([path('rtc/copywrite/', copywrite_ws_router)]),
+ }
+)
@@ -181,6 +181,21 @@ TEMPLATES = [
]
WSGI_APPLICATION = 'backend.wsgi.application'
+ASGI_APPLICATION = 'backend.asgi.application'
+
+CHANNEL_LAYERS = {
+ 'default': {
+ 'BACKEND': 'channels_redis.core.RedisChannelLayer',
+ 'CONFIG': {
+ 'hosts': [
+ (
+ env.str('CHANNELS_MDB_HOST', 'channels-mdb'),
+ env.int('CHANNELS_MDB_PORT', 6379),
+ )
+ ],
+ },
+ },
+}
# Database
DATABASES = {
@@ -195,6 +210,13 @@ DATABASES = {
}
}
+JWT_SETTINGS = {
+ 'secret_key': SECRET_KEY,
+ 'algorithm': env.str('JWT_ALGORITHM', 'HS256'),
+ 'access_expiry_time': env.timedelta('JWT_ACCESS_TOKEN_LIFETIME', 60 * 60 * 24),
+ 'refresh_expiry_time': env.timedelta('JWT_REFRESH_TOKEN_LIFETIME', 60 * 60 * 24),
+ 'encode_attributes': ['uid'],
+}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
@@ -3,9 +3,14 @@ from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
+from ninja import NinjaAPI
from backend.public import urlpatterns as public_urlpatterns
+api = NinjaAPI(title='AIR API', version='2.0.0')
+api.add_router('copywrite/', 'tools.copywrite.router.router')
+
+
urlpatterns = [
path('ml_models/', include('ml_model.urls', namespace='ml_model')),
path('stories/', include('stories.urls')),
@@ -15,7 +20,6 @@ urlpatterns = [
path('reports/', include('reports.urls')),
path('achievements/', include('achievements.urls')),
path('chats/', include('tools.chats.urls')),
- path('copywrite/', include('tools.copywrite.urls')),
path('feed/', include('tools.feed.urls')),
path('media/', include('tools.media.urls')),
path('schema/', SpectacularAPIView.as_view(), name='schema'),
@@ -32,6 +36,7 @@ urlpatterns = [
'public/',
SpectacularSwaggerView.as_view(url_name='schema-public'),
),
+ path('api/', api.urls),
]
urlpatterns += public_urlpatterns
@@ -0,0 +1,3 @@
+from typing import Dict, List
+
+PrimitiveType = str | int | float | List | Dict
@@ -11,8 +11,9 @@ from ml_model.models import (
ModelParameter,
ModelVersion,
NeuronModel,
- # ModelPaymentRule
)
+
+# ModelPaymentRule
from payments.services.payment_plan_service import PaymentPlanService
@@ -23,9 +24,6 @@ class SimpleService(ABC):
self.store = store
self.translator = Translator()
- @classmethod
- def init_relations(cls): ...
-
@property
@abstractmethod
def title(self) -> str: ...
@@ -1,8 +1,10 @@
import time
from datetime import timedelta
from decimal import Decimal
-from io import BytesIO
+from io import BufferedReader, BytesIO
+from typing import Any, Dict, List, Optional
+import httpx
from django.core.files.uploadedfile import InMemoryUploadedFile
from langchain import hub
from langchain.agents import AgentExecutor, create_structured_chat_agent
@@ -313,3 +315,21 @@ class Chatgpt(SimpleService):
[chat_history.chat_memory.messages[-1]], process_time, save
)
return msgs
+
+ @classmethod
+ def evaluate(
+ cls,
+ content: str,
+ image: Optional[BufferedReader] = None,
+ context_messages: List[str] = [],
+ *,
+ stream: bool = False,
+ parameters: List[Dict[str, Any]] = [],
+ ):
+ with httpx.Client() as client:
+ data = {}
+ if not stream:
+ return client.post('https://openai.com/api/chat/completions', json=data)
+ resp = client.post(
+ 'https://openai.com/api/chat/completions', json=data | {'stream': True}
+ )
@@ -3,7 +3,7 @@ import json
# import uuid
from io import BytesIO
-from typing import IO, Any
+from typing import IO, Any, Dict
import deepl
import replicate
@@ -148,3 +148,7 @@ def claude_run(payload: dict[str, Any]):
'https://api.anthropic.com/v1/messages', json.dumps(payload), headers=headers
).content
)
+
+
+@shared_task
+def evaluate_model(model_name: str, data: Dict[str, Any]): ...
@@ -1,3 +1,4 @@
+from decimal import Decimal
from uuid import UUID
from celery import shared_task
@@ -5,29 +6,18 @@ from celery.utils.log import get_task_logger
from django.db.models import F
from authentication.models.business_host import BusinessUserHost
-from authentication.selectors.user_selector import UserSelector
+from authentication.models.user import CustomUserModel
from authentication.services.email_service import EmailService
-from payments.selectors.payment_plan_selector import PaymentPlanSelector
-from payments.services.payment_service import PaymentService
+from payments.services.payment_plan_service import PaymentPlanService
logger = get_task_logger(__name__)
-@shared_task(name='conduct_recurrent', bind=True)
-def conduct_recurrent_payment(self, payment_plan_uid: str, user_id: str):
- logger.info('START %s %s' % (conduct_recurrent_payment.__name__, self.request.id))
- user = UserSelector.get_by_uid(UUID(user_id))
- logger.info('USER: %s' % user)
- payment_plan = PaymentPlanSelector(user).get_payment_plan_by_id(UUID(payment_plan_uid))
- logger.info('PLAN: %s' % payment_plan)
- PaymentService(user).conduct_recurring_payment(payment_plan, 'Ежемесячный платеж по подписке AIR')
- logger.info('FINISH %s %s' % (conduct_recurrent_payment.__name__, self.request.id))
-
-
@shared_task
def send_low_balance_message():
hosts = BusinessUserHost.objects.filter(
- token_cap_enabled=True, token_cap__gt=F('user__payment_plan__current_token_balance')
+ token_cap_enabled=True,
+ token_cap__gt=F('user__payment_plan__current_token_balance'),
)
for host in hosts:
for email in host.token_cap_emails:
@@ -36,3 +26,9 @@ def send_low_balance_message():
'Для пополнения обратитесь по контактам, указанным в договоре',
email,
)
+
+
+@shared_task
+def withdraw(user_id: UUID, amount: Decimal):
+ user = CustomUserModel.objects.get(uid=user_id)
+ PaymentPlanService(user).update_per_token_plan_details(amount)
@@ -1,13 +1,11 @@
-# Generated by Django 4.2.13 on 2024-06-12 21:43
+# Generated by Django 5.0.9 on 2024-12-22 13:19
-from django.conf import settings
-import django.contrib.postgres.fields
-from django.db import migrations, models
import django.db.models.deletion
-import django.utils.timezone
import django_minio_backend.models
import tools.copywrite.models
import uuid
+from django.conf import settings
+from django.db import migrations, models
class Migration(migrations.Migration):
@@ -15,54 +13,109 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
+ ('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
- name='Category',
+ name='Copywrite',
+ fields=[
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='ID')),
+ ('output_content', models.TextField(blank=True, null=True, verbose_name='Исходящий промпт')),
+ ('starred', models.BooleanField(default=False, verbose_name='Добавлено в избранное')),
+ ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Когда создано')),
+ ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype')),
+ ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='user_copywrites', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
+ ],
+ options={
+ 'verbose_name': 'Копирайт',
+ 'verbose_name_plural': 'Копирайты',
+ },
+ ),
+ migrations.CreateModel(
+ name='TemplateCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(default='Общее', max_length=50, verbose_name='Название')),
+ ('slug', models.CharField(max_length=50, verbose_name='Ярлык')),
],
options={
'verbose_name': 'Категория',
'verbose_name_plural': 'Категории',
},
),
+ migrations.CreateModel(
+ name='SelfCopywrite',
+ fields=[
+ ('copywrite_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='copywrite.copywrite')),
+ ('input_content', models.TextField(blank=True, null=True, verbose_name='Входящий промпт')),
+ ],
+ options={
+ 'verbose_name': 'Самописный копирайт',
+ 'verbose_name_plural': 'Самописные копирайты',
+ },
+ bases=('copywrite.copywrite',),
+ ),
migrations.CreateModel(
name='Template',
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=50, unique=True, verbose_name='Название')),
- ('description', models.CharField(max_length=200, verbose_name='Описание')),
- ('picture', models.FileField(null=True, storage=django_minio_backend.models.MinioBackend(bucket_name='air-templates-pictures'), upload_to=tools.copywrite.models.template_picture_upload, verbose_name='Картинка')),
- ('theme', models.CharField(max_length=200, verbose_name='Тема')),
- ('content', models.CharField(max_length=2048, verbose_name='Контент')),
- ('target_audience', models.CharField(max_length=100, verbose_name='Целевая аудитория')),
- ('resources_urls', django.contrib.postgres.fields.ArrayField(base_field=models.URLField(max_length=300, verbose_name='Ссылка на сайт'), help_text='Сайты, откуда брать информацию', size=None, verbose_name='Интернет-ресурсы')),
- ('keywords', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=300, verbose_name='Ключевое слово'), size=None, verbose_name='Ключевые слова')),
- ('tov', models.CharField(max_length=100, verbose_name='Тон голоса')),
- ('language', models.CharField(choices=[('ru', 'Русский'), ('en', 'Английский'), ('ge', 'Немецкий'), ('it', 'Итальянский'), ('fr', 'Французский')], max_length=20, verbose_name='Язык для копирайта')),
- ('params', models.JSONField(default=dict, verbose_name='Параметры для GPT')),
- ('categories', models.ManyToManyField(related_name='templates_copyright', to='copywrite.category', verbose_name='Категория')),
+ ('description', models.CharField(blank=True, max_length=200, null=True, verbose_name='Описание')),
+ ('picture', models.FileField(blank=True, null=True, storage=django_minio_backend.models.MinioBackend(bucket_name='air-templates-pictures'), upload_to=tools.copywrite.models.template_picture_uploader, verbose_name='Картинка')),
+ ('content', models.TextField(help_text='Синтаксис в этом поле зависит от Движка шаблонов', verbose_name='Контент')),
+ ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='templates_categories', to='copywrite.templatecategory', verbose_name='Категория')),
],
options={
'verbose_name': 'Шаблон',
'verbose_name_plural': 'Шаблоны',
+ 'ordering': ('order',),
+ 'abstract': False,
},
),
migrations.CreateModel(
- name='Copywrite',
+ name='TemplateVariable',
+ fields=[
+ ('order', models.PositiveIntegerField(db_index=True, editable=False, verbose_name='order')),
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100, verbose_name='Название')),
+ ('sysname', models.CharField(max_length=100, verbose_name='Системное название')),
+ ('default_value', models.JSONField(blank=True, null=True, verbose_name='Стандартное значение')),
+ ('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='template_variables', to='copywrite.template', verbose_name='Шаблон')),
+ ],
+ options={
+ 'verbose_name': 'Переменная шаблона',
+ 'verbose_name_plural': 'Переменные шаблона',
+ 'ordering': ('order',),
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='TemplateCopywrite',
+ fields=[
+ ('copywrite_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='copywrite.copywrite')),
+ ('template', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='template_copywrites', to='copywrite.template', verbose_name='Шаблон')),
+ ],
+ options={
+ 'verbose_name': 'Шаблонный копирайт',
+ 'verbose_name_plural': 'Шаблонные копирайты',
+ },
+ bases=('copywrite.copywrite',),
+ ),
+ migrations.CreateModel(
+ name='OverridenVariable',
fields=[
- ('uid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='Идентификатор')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, editable=False, verbose_name='Когда создан')),
- ('user', models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(app_label)s_%(class)s_user', related_query_name='%(app_label)s_%(class)ss_user', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='ID')),
+ ('value', models.JSONField(verbose_name='Значение')),
+ ('variable', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='template_variable_overriden_variables', to='copywrite.templatevariable', verbose_name='Переменная')),
+ ('copywrite', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='template_copywrite_overriden_variables', to='copywrite.templatecopywrite', verbose_name='Копирайт')),
],
options={
- 'verbose_name': 'Хранилище',
- 'verbose_name_plural': 'Копирайт-хранилища',
- 'ordering': ['-created_at'],
+ 'verbose_name': 'Переопределенная переменная',
+ 'verbose_name_plural': 'Переопределенные переменные',
+ 'unique_together': {('copywrite', 'variable')},
},
),
]
@@ -0,0 +1,77 @@
+from typing import Any, Generator, Literal
+from uuid import UUID
+
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import Q, QuerySet
+
+from authentication.models.user import CustomUserModel
+from core.typing import PrimitiveType
+from tools.copywrite.models import Copywrite, OverridenVariable
+
+
+class CopywriteService:
+ @classmethod
+ def list_copywrites_by_user(
+ cls,
+ user: CustomUserModel,
+ filters: Q = Q(),
+ ordering: Literal['starred'] = 'starred',
+ ) -> QuerySet[Copywrite]:
+ return (
+ Copywrite.objects.filter(user=user)
+ .filter(filters)
+ .prefetch_related(
+ 'templatecopywrite__template',
+ 'templatecopywrite__template__template_variables',
+ 'polymorphic_ctype',
+ )
+ .order_by(ordering)
+ )
+
+ @classmethod
+ def create_draft(
+ cls,
+ user: CustomUserModel,
+ type: Literal['template', 'self'],
+ initial: dict[str, Any],
+ ) -> Copywrite:
+ klass = ContentType.objects.get_by_natural_key(
+ 'copywrite', f'{type}copywrite'
+ ).model_class()
+ return klass.objects.create(user=user, **initial)
+
+ @classmethod
+ def get_copywrite_by_id(cls, copywrite_id: UUID):
+ return Copywrite.objects.prefetch_related(
+ 'templatecopywrite__template',
+ 'templatecopywrite__template__template_variables',
+ 'polymorphic_ctype__model',
+ ).get(id=copywrite_id)
+
+ @classmethod
+ def mark_as_starred(cls, copywrite_id: UUID) -> None:
+ Copywrite.objects.filter(id=copywrite_id).update(starred=True)
+
+ @classmethod
+ def override_variable(
+ cls, copywrite_id: UUID, variable_id: UUID, value: PrimitiveType
+ ) -> OverridenVariable:
+ return OverridenVariable.objects.create(
+ copywrite_id=copywrite_id, variable_id=variable_id, value=value
+ )
+
+ @classmethod
+ def update_overriden_variable(
+ cls, variable_id: UUID, value: PrimitiveType
+ ) -> OverridenVariable:
+ ov = OverridenVariable.objects.get(id=variable_id)
+ ov.value = value
+ ov.save()
+ return ov
+
+ @classmethod
+ def remove_overriden_variable(cls, variable_id: UUID) -> None:
+ OverridenVariable.objects.get(id=variable_id).delete()
+
+ @classmethod
+ def generate(cls, copywrite_id: UUID) -> Generator[str, None, None]: ...
@@ -0,0 +1,19 @@
+from uuid import UUID
+
+from django.db.models import Q, QuerySet
+
+from tools.copywrite.models import Template, TemplateCategory
+
+
+class TemplateService:
+ @classmethod
+ def list_templates(cls, filters: Q = Q()) -> QuerySet[Template]:
+ return Template.objects.filter(filters)
+
+ @classmethod
+ def get_template_by_id(cls, template_id: UUID) -> Template:
+ return Template.objects.prefetch_related('template_variables').get(id=template_id)
+
+ @classmethod
+ def list_categories(cls, filters: Q = Q()) -> QuerySet[TemplateCategory]:
+ return TemplateCategory.objects.filter(filters)
@@ -1,42 +1,52 @@
from django.contrib import admin
+from polymorphic.admin import (
+ PolymorphicChildModelAdmin,
+ PolymorphicChildModelFilter,
+ PolymorphicParentModelAdmin,
+)
-from messages.inlines import MessageInline
+from tools.copywrite.models import (
+ Copywrite,
+ OverridenVariable,
+ SelfCopywrite,
+ Template,
+ TemplateCategory,
+ TemplateCopywrite,
+ TemplateVariable,
+)
-from .models import Category, Copywrite, Template
+class CopywriteChildAdmin(PolymorphicChildModelAdmin): ...
-@admin.register(Copywrite)
-class CopywriteAdmin(admin.ModelAdmin):
- list_display = ['_user', 'messages_count']
- raw_id_fields = ['user']
- inlines = [
- MessageInline,
- ]
- @admin.display(description='Пользователь')
- def _user(self, obj):
- return obj.user.email
+@admin.register(SelfCopywrite)
+class SelfCopywriteAdmin(CopywriteChildAdmin): ...
- @admin.display(description='Кол-во сообщений')
- def messages_count(self, obj: Copywrite):
- return obj.all_messages.count()
+class OverridenVariableInline(admin.TabularInline):
+ model = OverridenVariable
-@admin.register(Category)
-class CategoryAdmin(admin.ModelAdmin):
- list_display = ['title', '_templates']
- @admin.display(description='Подключенные шаблоны')
- def _templates(self, obj):
- return ','.join([str(m) for m in obj.templates])
+@admin.register(TemplateCopywrite)
+class TemplateCopywriteAdmin(CopywriteChildAdmin):
+ inlines = (OverridenVariableInline,)
-@admin.register(Template)
-class TemplateAdmin(admin.ModelAdmin):
- list_display = ['title', 'description', '_picture']
+@admin.register(Copywrite)
+class CopywriteAdmin(PolymorphicParentModelAdmin):
+ child_models = (SelfCopywrite, TemplateCopywrite)
+ list_filter = (PolymorphicChildModelFilter,)
+
- @admin.display(description='Миниатюра')
- def _picture(self, obj):
- from django.utils.html import format_html
+@admin.register(TemplateCategory)
+class TemplateCategoryAdmin(admin.ModelAdmin): ...
- return format_html('
', obj.picture)
+
+class TemplateVariableInline(admin.TabularInline):
+ model = TemplateVariable
+
+
+@admin.register(Template)
+class TemplateAdmin(admin.ModelAdmin):
+ list_display = ('id', 'title', 'category')
+ inlines = (TemplateVariableInline,)
@@ -1,169 +0,0 @@
-from django.http import StreamingHttpResponse
-from drf_spectacular.utils import OpenApiParameter, extend_schema
-from rest_framework.permissions import IsAuthenticated
-from rest_framework.response import Response
-from rest_framework.views import APIView
-
-from messages.models import Message
-from messages.serializers import MessageSerializer
-from ml_model.models import NeuronModel
-from ml_model.services.chatgpt import Chatgpt
-
-from .models import Category, Copywrite, Template
-from .serializers import (
- CategorySerializer,
- TemplateCreateSerializer,
- TemplateSerializer,
- TemplatesSerializer,
-)
-
-
-class CategoriesAPIView(APIView):
- @extend_schema(responses={200: CategorySerializer(many=True)})
- def get(self, request, *args, **kwargs):
- """
- Get categories
- """
- categories = Category.objects.all()
- return Response(CategorySerializer(categories, many=True).data, 200)
-
-
-class TemplatesAPIView(APIView):
- @extend_schema(responses={200: TemplatesSerializer(many=True)})
- def get(self, request, *args, **kwargs):
- """
- Get templates
- """
- templates = Template.objects.all()
- return Response(TemplatesSerializer(templates, many=True).data, 200)
-
- @extend_schema(request=TemplateCreateSerializer, responses={201: TemplateSerializer})
- def post(self, request, *args, **kwargs):
- """
- Create custom template for users via CopywriteTool and staff-user
- """
- if request.user.is_superuser:
- serializer = TemplateCreateSerializer(data=request.data)
- if serializer.is_valid():
- template = Template.objects.create(**serializer.validated_data)
- return Response(TemplateSerializer(template).data, 201)
- return Response("Default user can't reach this action, please relog", 403)
-
-
-class TemplateAPIView(APIView):
- @extend_schema(responses={200: TemplateSerializer})
- def get(self, request, template_id, *args, **kwargs):
- """
- Get template by id
- """
- template = Template.objects.get(id=template_id)
- return Response(TemplateSerializer(template).data, 200)
-
-
-class CopywritesAPIView(APIView):
- permission_classes = [
- IsAuthenticated,
- ]
-
- @extend_schema(
- parameters=[
- OpenApiParameter(
- 'model',
- str,
- 'path',
- required=True,
- )
- ],
- responses={200: MessageSerializer(many=True)},
- )
- def get(self, request, *args, **kwargs):
- """
- List Messages
- """
- copywrite, _ = Copywrite.objects.get_or_create(
- user=request.user, defaults={'user': request.user}
- )
- return Response(MessageSerializer(copywrite.output_messages, many=True).data, 200)
-
- @extend_schema(
- parameters=[OpenApiParameter('stream', bool)],
- request=MessageSerializer,
- responses={
- 200: MessageSerializer(many=True),
- },
- )
- def post(self, request, *args, **kwargs):
- serializer = MessageSerializer(data=request.data)
- if serializer.is_valid():
- stream = bool(request.query_params.get('stream'))
- if not serializer.validated_data.get('info'):
- serializer.validated_data['info'] = {}
- serializer.validated_data['info']['model'] = 'gpt-3.5-turbo'
- copywrite, _ = Copywrite.objects.get_or_create(
- user=request.user, defaults={'user': request.user}
- )
- service = Chatgpt
- input_message = Message.objects.create(
- **serializer.validated_data,
- content_object=copywrite,
- from_model=False,
- )
- setattr(
- input_message.content_object,
- 'model',
- NeuronModel.objects.get(slug='chatgpt'),
- )
- if stream:
- output_messages = service(copywrite).stream(input_message)
- return StreamingHttpResponse(
- output_messages, content_type='text/event-stream'
- )
- else:
- try:
- output_messages = service(copywrite).make(input_message)
- except Exception as e:
- input_message.is_sent = False
- input_message.save()
- return Response(f'Error: {e}', 400)
- return Response(MessageSerializer(output_messages, many=True).data, 200)
- else:
- return Response(serializer.errors, 400)
-
-
-class CopywriteAPIView(APIView):
- permission_classes = [
- IsAuthenticated,
- ]
-
- @extend_schema(
- request=None,
- responses={
- 204: None,
- },
- )
- def put(self, request, message_uid, *args, **kwargs):
- """
- Put message into favourite
- """
- copywrite = Copywrite.objects.get(user=request.user)
- message = Message.objects.get(
- uid=message_uid, chats_chats_messages=copywrite, is_deleted=False
- )
- message.is_favourite = True
- message.save()
- return Response(status=204)
-
- @extend_schema(
- responses={204: None},
- )
- def delete(self, request, message_uid, *args, **kwargs):
- """
- Hide message
- """
- copywrite = Copywrite.objects.get(user=request.user)
- message = Message.objects.get(
- pk=message_uid, copyright_copyrights_messages=copywrite, is_deleted=False
- )
- message.is_deleted = True
- message.save()
- return Response(status=204)
@@ -0,0 +1 @@
+COPYWRITE_WS_KEY = 'copywrite_%s'
@@ -0,0 +1,29 @@
+from logging import getLogger
+
+from channels.generic.websocket import AsyncJsonWebsocketConsumer
+from channels_redis.core import RedisChannelLayer
+
+from tools.copywrite.constants import COPYWRITE_WS_KEY
+
+logger = getLogger(__name__)
+
+
+class CopywriteConsumer(AsyncJsonWebsocketConsumer):
+ channel_layer: RedisChannelLayer
+
+ async def connect(self):
+ await self.channel_layer.group_add(
+ COPYWRITE_WS_KEY % f"{self.scope['url_route']['kwargs']['copywrite_id']}",
+ self.channel_name,
+ )
+ return await self.accept()
+
+ async def close(self, code=None, reason=None):
+ await self.channel_layer.group_discard(
+ COPYWRITE_WS_KEY % f"{self.scope['url_route']['kwargs']['copywrite_id']}",
+ self.channel_name,
+ )
+ return await super().close(code, reason)
+
+ async def generate_chunk_event(self, event_data):
+ return await self.send_json(event_data)
@@ -1,84 +1,183 @@
-from django.contrib.postgres.fields import ArrayField
+from uuid import uuid4
+
+from django.contrib.auth import get_user_model
from django.db import models
from django.db.models import QuerySet
-from django.utils import timezone
from django_minio_backend import MinioBackend
-
-from messages.models import SingleStore
-
-
-def template_picture_upload(instance: 'Template', filename: str):
- return filename
+from ordered_model.models import OrderedModel
+from polymorphic.models import PolymorphicManager, PolymorphicModel
-class Copywrite(SingleStore):
- created_at = models.DateTimeField(default=timezone.now, verbose_name='Когда создан', editable=False)
+def template_picture_uploader(instance: 'Template', filename: str):
+ return f'{instance.title}/{filename}'
- @property
- def output_messages(self):
- return self.messages.filter(from_model=True)
-
- def __str__(self) -> str:
- return f'Копирайт-хранилище пользователя {self.user}'
- class Meta:
- verbose_name = 'Хранилище'
- verbose_name_plural = 'Копирайт-хранилища'
- ordering = ['-created_at']
-
-
-class Category(models.Model):
+class TemplateCategory(models.Model):
title = models.CharField(max_length=50, default='Общее', verbose_name='Название')
+ slug = models.CharField(max_length=50, verbose_name='Ярлык')
def __str__(self) -> str:
return self.title
@property
def templates(self) -> QuerySet['Template']:
- return self.templates_copyright.all()
+ return self.templates_categories.all()
class Meta:
verbose_name = 'Категория'
verbose_name_plural = 'Категории'
-class Template(models.Model):
- class LanguageChoices(models.TextChoices):
- RU = ('ru', 'Русский')
- EN = ('en', 'Английский')
- GE = ('ge', 'Немецкий')
- IT = ('it', 'Итальянский')
- FR = ('fr', 'Французский')
+class Template(OrderedModel):
+ id = models.UUIDField(
+ primary_key=True, default=uuid4, editable=False, verbose_name='ID'
+ )
title = models.CharField(max_length=50, unique=True, verbose_name='Название')
- description = models.CharField(max_length=200, verbose_name='Описание')
+ description = models.CharField(
+ null=True, blank=True, max_length=200, verbose_name='Описание'
+ )
picture = models.FileField(
verbose_name='Картинка',
storage=MinioBackend(bucket_name='air-templates-pictures'),
- upload_to=template_picture_upload,
+ upload_to=template_picture_uploader,
null=True,
+ blank=True,
)
- categories = models.ManyToManyField(
- 'Category', verbose_name='Категория', related_name='templates_copyright'
+ category = models.ForeignKey(
+ TemplateCategory,
+ on_delete=models.PROTECT,
+ verbose_name='Категория',
+ related_name='templates_categories',
)
- theme = models.CharField('Тема', max_length=200)
- content = models.CharField('Контент', max_length=2048)
- target_audience = models.CharField('Целевая аудитория', max_length=100)
- resources_urls = ArrayField(
- verbose_name='Интернет-ресурсы',
- help_text='Сайты, откуда брать информацию',
- base_field=models.URLField('Ссылка на сайт', max_length=300),
- )
- keywords = ArrayField(
- verbose_name='Ключевые слова', base_field=models.CharField('Ключевое слово', max_length=300)
+
+ content = models.TextField(
+ verbose_name='Контент',
+ help_text='Синтаксис в этом поле зависит от Движка шаблонов',
)
- tov = models.CharField('Тон голоса', max_length=100)
- language = models.CharField('Язык для копирайта', max_length=20, choices=LanguageChoices.choices)
- params = models.JSONField(verbose_name='Параметры для GPT', default=dict)
+
+ @property
+ def variables(self) -> QuerySet['TemplateVariable']:
+ return self.template_variables.all()
+
+ @property
+ def copywrites(self) -> QuerySet['TemplateCopywrite']:
+ return self.template_copywrites.all()
def __str__(self) -> str:
return self.title
- class Meta:
+ class Meta(OrderedModel.Meta):
verbose_name = 'Шаблон'
verbose_name_plural = 'Шаблоны'
+
+
+class TemplateVariable(OrderedModel):
+ id = models.UUIDField(
+ primary_key=True, default=uuid4, editable=False, verbose_name='ID'
+ )
+
+ template = models.ForeignKey(
+ Template,
+ on_delete=models.CASCADE,
+ verbose_name='Шаблон',
+ related_name='template_variables',
+ )
+
+ name = models.CharField(max_length=100, verbose_name='Название')
+ sysname = models.CharField(max_length=100, verbose_name='Системное название')
+ default_value = models.JSONField(
+ null=True, blank=True, verbose_name='Стандартное значение'
+ )
+
+ class Meta(OrderedModel.Meta):
+ verbose_name = 'Переменная шаблона'
+ verbose_name_plural = 'Переменные шаблона'
+
+
+class Copywrite(PolymorphicModel):
+ id = models.UUIDField(
+ primary_key=True, default=uuid4, editable=False, verbose_name='ID'
+ )
+ user = models.ForeignKey(
+ get_user_model(),
+ on_delete=models.SET_NULL,
+ null=True,
+ blank=True,
+ verbose_name='Пользователь',
+ related_name='user_copywrites',
+ )
+ output_content = models.TextField(
+ null=True, blank=True, verbose_name='Исходящий промпт'
+ )
+ starred = models.BooleanField(default=False, verbose_name='Добавлено в избранное')
+ created_at = models.DateTimeField(auto_now_add=True, verbose_name='Когда создано')
+
+ objects = PolymorphicManager()
+
+ @property
+ def type(self) -> str:
+ return self.polymorphic_ctype.model[
+ : self.polymorphic_ctype.model.index('copywrite')
+ ]
+
+ @property
+ def generated(self):
+ return self.generated_at is not None and (
+ self.output_content is None or self.output_content == ''
+ )
+
+ class Meta:
+ verbose_name = 'Копирайт'
+ verbose_name_plural = 'Копирайты'
+
+
+class SelfCopywrite(Copywrite):
+ input_content = models.TextField(
+ null=True, blank=True, verbose_name='Входящий промпт'
+ )
+
+ class Meta:
+ verbose_name = 'Самописный копирайт'
+ verbose_name_plural = 'Самописные копирайты'
+
+
+class TemplateCopywrite(Copywrite):
+ template = models.ForeignKey(
+ Template,
+ on_delete=models.CASCADE,
+ verbose_name='Шаблон',
+ related_name='template_copywrites',
+ )
+
+ @property
+ def overriden_variables(self) -> QuerySet['OverridenVariable']:
+ return self.template_copywrite_overriden_variables.all()
+
+ class Meta:
+ verbose_name = 'Шаблонный копирайт'
+ verbose_name_plural = 'Шаблонные копирайты'
+
+
+class OverridenVariable(models.Model):
+ id = models.UUIDField(
+ primary_key=True, default=uuid4, editable=False, verbose_name='ID'
+ )
+ variable = models.ForeignKey(
+ TemplateVariable,
+ on_delete=models.PROTECT,
+ verbose_name='Переменная',
+ related_name='template_variable_overriden_variables',
+ )
+ copywrite = models.ForeignKey(
+ TemplateCopywrite,
+ on_delete=models.CASCADE,
+ verbose_name='Копирайт',
+ related_name='template_copywrite_overriden_variables',
+ )
+ value = models.JSONField(verbose_name='Значение')
+
+ class Meta:
+ verbose_name = 'Переопределенная переменная'
+ verbose_name_plural = 'Переопределенные переменные'
+ unique_together = ['copywrite', 'variable']
@@ -0,0 +1,118 @@
+from typing import List, Literal
+from uuid import UUID
+
+from channels.routing import URLRouter
+from django.urls import re_path
+from ninja import Query, Router
+
+from authentication.security import SyncAuthBearer
+from payments.tasks import withdraw as withdraw_task
+from tools.copywrite.consumers import CopywriteConsumer
+from tools.copywrite.schemas import (
+ CopywriteFilterSchema,
+ CreateDraftSchema,
+ CreateOverridenVariableSchema,
+ OverridenVariableSchema,
+ SelfCopywriteSchema,
+ TemplateCategorySchema,
+ TemplateCopywriteSchema,
+ TemplateFilterSchema,
+ TemplateSchema,
+ TemplateShortSchema,
+ UpdateOverridenVariableSchema,
+)
+from tools.copywrite.services.copywrite import CopywriteService
+from tools.copywrite.services.template import TemplateService
+
+from .tasks import generate as generate_task
+
+router = Router(tags=['copywrite'], auth=SyncAuthBearer())
+
+ws_router = URLRouter(
+ [
+ re_path(
+ 'copywrites/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}|)/$',
+ CopywriteConsumer.as_asgi(),
+ )
+ ]
+)
+
+
+@router.get(
+ 'copywrites/',
+ tags=['copywrite/copywrites'],
+ response=List[TemplateCopywriteSchema | SelfCopywriteSchema],
+)
+def list_copywrites(
+ request,
+ filters: CopywriteFilterSchema = Query(...),
+ ordering: Literal['starred'] = 'starred',
+):
+ return CopywriteService.list_copywrites_by_user(
+ user=request.auth, filters=filters.get_filter_expression(), ordering=ordering
+ )
+
+
+@router.post(
+ 'copywrites/',
+ tags=['copywrite/copywrites'],
+ response=TemplateCopywriteSchema | SelfCopywriteSchema,
+)
+def create_draft(request, data: CreateDraftSchema):
+ return CopywriteService.create_draft(user=request.auth, **data.model_dump())
+
+
+@router.get(
+ 'copywrites/{id}/',
+ tags=['copywrite/copywrites'],
+ response=TemplateCopywriteSchema | SelfCopywriteSchema,
+)
+def get_copywrite_by_id(request, id: UUID):
+ return CopywriteService.get_copywrite_by_id(copywrite_id=id)
+
+
+@router.put('copywrites/{id}/generate/', tags=['copywrite/copywrites'])
+def generate(request, id: UUID):
+ generate_task.delay(copywrite_id=id) | withdraw_task.delay()
+
+
+@router.put('copywrites/{id}/', tags=['copywrite/copywrites'])
+def update_copywrite(request, id: UUID):
+ """Не реализовано!!!"""
+ return CopywriteService.update()
+
+
+@router.post('variables/', tags=['copywrite/variables'], response=OverridenVariableSchema)
+def override_variable(request, data: CreateOverridenVariableSchema):
+ return CopywriteService.override_variable(**data.model_dump())
+
+
+@router.put('variables/{id}/', tags=['copywrite/variables'], response={204: None})
+def update_overriden_variable(request, id: UUID, data: UpdateOverridenVariableSchema):
+ return CopywriteService.update_overriden_variable(variable_id=id, **data.model_dump())
+
+
+@router.delete('variables/{id}/', tags=['copywrite/variables'], response={204: None})
+def remove_overriden_variable(request, id: UUID):
+ CopywriteService.remove_overriden_variable(variable_id=id)
+
+
+@router.get(
+ 'templates/categories/',
+ tags=['copywrite/templates'],
+ response=List[TemplateCategorySchema],
+)
+def list_template_categories(request):
+ return TemplateService.list_categories()
+
+
+@router.get(
+ 'templates/', tags=['copywrite/templates'], response=List[TemplateShortSchema]
+)
+def list_templates(request, filters: TemplateFilterSchema = Query(...)):
+ return TemplateService.list_templates(filters=filters.get_filter_expression())
+
+
+@router.get('templates/{id}/', tags=['copywrite/templates'], response=TemplateSchema)
+def get_template_by_id(request, id: UUID):
+ return TemplateService.get_template_by_id(template_id=id)
@@ -0,0 +1,113 @@
+from typing import Any, List, Literal
+from uuid import UUID
+
+from django.db.models import Q
+from ninja import Field, FilterSchema, ModelSchema, Schema
+
+from tools.copywrite.models import (
+ OverridenVariable,
+ SelfCopywrite,
+ Template,
+ TemplateCategory,
+ TemplateCopywrite,
+ TemplateVariable,
+)
+
+
+class CreateDraftSchema(Schema):
+ type: str
+ initial: dict[str, Any] = {}
+
+
+class TemplateVariableSchema(ModelSchema):
+ class Meta:
+ model = TemplateVariable
+ exclude = ('order', 'sysname', 'template')
+
+
+class TemplateShortSchema(ModelSchema):
+ class Meta:
+ model = Template
+ fields = ('id', 'title', 'description', 'picture')
+
+
+class TemplateCategorySchema(ModelSchema):
+ class Meta:
+ model = TemplateCategory
+ fields = ('title', 'slug')
+
+
+class TemplateSchema(ModelSchema):
+ variables: List[TemplateVariableSchema]
+
+ class Meta:
+ model = Template
+ exclude = ('order', 'content', 'category', 'description')
+
+
+class TemplateFilterSchema(FilterSchema):
+ category: List[str] = Field(None, q='category__slug__in')
+
+
+class BaseCopywriteSchema:
+ type: str
+
+
+class SelfCopywriteSchema(BaseCopywriteSchema, ModelSchema):
+ generated: bool
+
+ class Meta:
+ model = SelfCopywrite
+ exclude = (
+ 'copywrite_ptr',
+ 'user',
+ 'polymorphic_ctype',
+ 'created_at',
+ )
+
+
+class OverridenVariableSchema(ModelSchema):
+ class Meta:
+ model = OverridenVariable
+ fields = ('id', 'variable', 'value')
+
+
+class CreateOverridenVariableSchema(ModelSchema):
+ copywrite_id: UUID
+ variable_id: UUID
+
+ class Meta:
+ model = OverridenVariable
+ exclude = ('id', 'copywrite', 'variable')
+
+
+class TemplateCopywriteSchema(BaseCopywriteSchema, ModelSchema):
+ template: TemplateSchema
+ overriden_variables: List[OverridenVariableSchema]
+ generated: bool
+
+ class Meta:
+ model = TemplateCopywrite
+ exclude = (
+ 'copywrite_ptr',
+ 'user',
+ 'polymorphic_ctype',
+ 'created_at',
+ )
+
+
+class CopywriteFilterSchema(FilterSchema):
+ types: List[Literal['template', 'self']] = []
+
+ def filter_types(self, value: List[Literal['template', 'self']]):
+ if value:
+ return Q(
+ polymorphic_ctype__model__in=list(map(lambda x: f'{x}copywrite', value))
+ )
+ return Q()
+
+
+class UpdateOverridenVariableSchema(ModelSchema):
+ class Meta:
+ model = OverridenVariable
+ fields = ('value',)
@@ -1,29 +0,0 @@
-from rest_framework import serializers
-
-from .models import Category, Template
-
-
-class CategorySerializer(serializers.ModelSerializer):
- class Meta:
- model = Category
- fields = '__all__'
-
-
-class TemplatesSerializer(serializers.ModelSerializer):
- class Meta:
- model = Template
- exclude = ('categories', 'params')
-
-
-class TemplateSerializer(serializers.ModelSerializer):
- class Meta:
- model = Template
- exclude = ('categories',)
-
-
-class TemplateCreateSerializer(serializers.ModelSerializer):
- category = serializers.PrimaryKeyRelatedField(queryset=Category.objects.all())
-
- class Meta:
- model = Template
- fields = '__all__'
@@ -0,0 +1,18 @@
+from uuid import UUID
+
+from celery import shared_task
+from channels.layers import get_channel_layer
+from channels_redis.core import RedisChannelLayer
+
+from tools.copywrite.constants import COPYWRITE_WS_KEY
+from tools.copywrite.services.copywrite import CopywriteService
+
+
+@shared_task
+def generate(copywrite_id: UUID):
+ layer: RedisChannelLayer = get_channel_layer()
+ for chunk in CopywriteService.generate(copywrite_id=copywrite_id):
+ layer.group_send(
+ COPYWRITE_WS_KEY.format(copywrite_id),
+ {'type': 'generate.chunk.event', 'event_data': {'chunk': chunk}},
+ )
@@ -1,14 +0,0 @@
-from django.urls import path
-
-from .apis import CopywriteAPIView, CopywritesAPIView, TemplateAPIView, TemplatesAPIView
-
-urlpatterns = [
- path('', CopywritesAPIView.as_view(), name='copywrite'),
- path(
- '/',
- CopywriteAPIView.as_view(),
- name='copywrite-message',
- ),
- path('templates/', TemplatesAPIView.as_view()),
- path('templates/', TemplateAPIView.as_view()),
-]
@@ -73,3 +73,7 @@ MAIN_SITE_URL=https://app.air.fail
DJANGO_SUPERUSER_EMAIL=example@root.ru
DJANGO_SUPERUSER_USERNAME=root
DJANGO_SUPERUSER_PASSWORD=root
+
+# DJANGO CHANNELS
+CHANNELS_HOST_MDB=channels-mdb
+CHANNELS_PORT_MDB=6379
\ No newline at end of file
@@ -10,11 +10,10 @@ services:
- python manage.py initialize_buckets &&
python manage.py collectstatic --no-input && python manage.py migrate &&
(python manage.py createsuperuser --no-input || true) &&
- python -m gunicorn --bind 0.0.0.0:8000 --timeout 300 backend.wsgi:application --log-level debug --threads 1 -w 1 --reload
+ python -m uvicorn --host 0.0.0.0 --workers 1 --timeout-keep-alive 300 --ws wsproto --loop uvloop --http httptools --lifespan off backend.asgi:application --log-level debug --reload
volumes:
- .:/code
ports:
- - "5678:5678"
- "8000:8000"
env_file:
- .env
@@ -31,6 +30,10 @@ services:
image: redis:alpine
restart: unless-stopped
+ channels-mdb:
+ image: redis:alpine
+ restart: unless-stopped
+
celery:
restart: unless-stopped
build:
@@ -14,7 +14,7 @@ services:
- |
python manage.py collectstatic --no-input
python manage.py compilemessages
- python -m gunicorn --bind 0.0.0.0:8000 --timeout 300 backend.wsgi:application --log-level info --threads 4 -w 8
+ python -m uvicorn --host 0.0.0.0 --workers 1 --timeout-keep-alive 300 --ws wsproto --loop uvloop --http httptools --lifespan off --log-level info backend.asgi:application
ports:
- "8000:8000"
env_file:
@@ -59,6 +59,11 @@ services:
restart: unless-stopped
container_name: celery-mdb
+ channels-mdb:
+ image: redis:alpine
+ restart: unless-stopped
+ container_name: channels-mdb
+
networks:
default:
name: "air"
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
[[package]]
name = "aiohappyeyeballs"
@@ -271,6 +271,49 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
+[[package]]
+name = "autobahn"
+version = "24.4.2"
+description = "WebSocket client & server library, WAMP real-time framework"
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "autobahn-24.4.2-py2.py3-none-any.whl", hash = "sha256:c56a2abe7ac78abbfb778c02892d673a4de58fd004d088cd7ab297db25918e81"},
+ {file = "autobahn-24.4.2.tar.gz", hash = "sha256:a2d71ef1b0cf780b6d11f8b205fd2c7749765e65795f2ea7d823796642ee92c9"},
+]
+
+[package.dependencies]
+cryptography = ">=3.4.6"
+hyperlink = ">=21.0.0"
+setuptools = "*"
+txaio = ">=21.2.1"
+
+[package.extras]
+all = ["PyGObject (>=3.40.0)", "argon2-cffi (>=20.1.0)", "attrs (>=20.3.0)", "base58 (>=2.1.0)", "bitarray (>=2.7.5)", "cbor2 (>=5.2.0)", "cffi (>=1.14.5)", "click (>=8.1.2)", "ecdsa (>=0.16.1)", "eth-abi (>=4.0.0)", "flatbuffers (>=22.12.6)", "hkdf (>=0.0.3)", "jinja2 (>=2.11.3)", "mnemonic (>=0.19)", "msgpack (>=1.0.2)", "passlib (>=1.7.4)", "py-ecc (>=5.1.0)", "py-eth-sig-utils (>=0.4.0)", "py-multihash (>=2.0.1)", "py-ubjson (>=0.16.1)", "pynacl (>=1.4.0)", "pyopenssl (>=20.0.1)", "python-snappy (>=0.6.0)", "pytrie (>=0.4.0)", "qrcode (>=7.3.1)", "rlp (>=2.0.1)", "service-identity (>=18.1.0)", "spake2 (>=0.8)", "twisted (>=20.3.0)", "twisted (>=24.3.0)", "u-msgpack-python (>=2.1)", "ujson (>=4.0.2)", "web3[ipfs] (>=6.0.0)", "xbr (>=21.2.1)", "yapf (==0.29.0)", "zlmdb (>=21.2.1)", "zope.interface (>=5.2.0)"]
+compress = ["python-snappy (>=0.6.0)"]
+dev = ["backports.tempfile (>=1.0)", "build (>=1.2.1)", "bumpversion (>=0.5.3)", "codecov (>=2.0.15)", "flake8 (<5)", "humanize (>=0.5.1)", "mypy (>=0.610)", "passlib", "pep8-naming (>=0.3.3)", "pip (>=9.0.1)", "pyenchant (>=1.6.6)", "pyflakes (>=1.0.0)", "pyinstaller (>=4.2)", "pylint (>=1.9.2)", "pytest (>=3.4.2)", "pytest-aiohttp", "pytest-asyncio (>=0.14.0)", "pytest-runner (>=2.11.1)", "pyyaml (>=4.2b4)", "qualname", "sphinx (>=1.7.1)", "sphinx-autoapi (>=1.7.0)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-images (>=0.9.1)", "tox (>=4.2.8)", "tox-gh-actions (>=2.2.0)", "twine (>=3.3.0)", "twisted (>=22.10.0)", "txaio (>=20.4.1)", "watchdog (>=0.8.3)", "wheel (>=0.36.2)", "yapf (==0.29.0)"]
+encryption = ["pynacl (>=1.4.0)", "pyopenssl (>=20.0.1)", "pytrie (>=0.4.0)", "qrcode (>=7.3.1)", "service-identity (>=18.1.0)"]
+nvx = ["cffi (>=1.14.5)"]
+scram = ["argon2-cffi (>=20.1.0)", "cffi (>=1.14.5)", "passlib (>=1.7.4)"]
+serialization = ["cbor2 (>=5.2.0)", "flatbuffers (>=22.12.6)", "msgpack (>=1.0.2)", "py-ubjson (>=0.16.1)", "u-msgpack-python (>=2.1)", "ujson (>=4.0.2)"]
+twisted = ["attrs (>=20.3.0)", "twisted (>=24.3.0)", "zope.interface (>=5.2.0)"]
+ui = ["PyGObject (>=3.40.0)"]
+xbr = ["base58 (>=2.1.0)", "bitarray (>=2.7.5)", "cbor2 (>=5.2.0)", "click (>=8.1.2)", "ecdsa (>=0.16.1)", "eth-abi (>=4.0.0)", "hkdf (>=0.0.3)", "jinja2 (>=2.11.3)", "mnemonic (>=0.19)", "py-ecc (>=5.1.0)", "py-eth-sig-utils (>=0.4.0)", "py-multihash (>=2.0.1)", "rlp (>=2.0.1)", "spake2 (>=0.8)", "twisted (>=20.3.0)", "web3[ipfs] (>=6.0.0)", "xbr (>=21.2.1)", "yapf (==0.29.0)", "zlmdb (>=21.2.1)"]
+
+[[package]]
+name = "automat"
+version = "24.8.1"
+description = "Self-service finite-state machines for the programmer on the go."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "Automat-24.8.1-py3-none-any.whl", hash = "sha256:bf029a7bc3da1e2c24da2343e7598affaa9f10bf0ab63ff808566ce90551e02a"},
+ {file = "automat-24.8.1.tar.gz", hash = "sha256:b34227cf63f6325b8ad2399ede780675083e439b20c323d376373d8ee6306d88"},
+]
+
+[package.extras]
+visualize = ["Twisted (>=16.1.1)", "graphviz (>0.5.1)"]
+
[[package]]
name = "billiard"
version = "4.2.0"
@@ -455,6 +498,47 @@ files = [
[package.dependencies]
pycparser = "*"
+[[package]]
+name = "channels"
+version = "4.2.0"
+description = "Brings async, event-driven capabilities to Django."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "channels-4.2.0-py3-none-any.whl", hash = "sha256:6b75bc8d6888fb7236e7e7bf1948520b72d296ad08216a242fc56b1db0ffde1a"},
+ {file = "channels-4.2.0.tar.gz", hash = "sha256:d9e707487431ba5dbce9af982970dab3b0efd786580fadb99e45dca5e39fdd59"},
+]
+
+[package.dependencies]
+asgiref = ">=3.6.0,<4"
+daphne = {version = ">=4.0.0", optional = true, markers = "extra == \"daphne\""}
+Django = ">=4.2"
+
+[package.extras]
+daphne = ["daphne (>=4.0.0)"]
+tests = ["async-timeout", "coverage (>=4.5,<5.0)", "pytest", "pytest-asyncio", "pytest-django"]
+
+[[package]]
+name = "channels-redis"
+version = "4.2.1"
+description = "Redis-backed ASGI channel layer implementation"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "channels_redis-4.2.1-py3-none-any.whl", hash = "sha256:2ca33105b3a04b5a327a9c47dd762b546f30b76a0cd3f3f593a23d91d346b6f4"},
+ {file = "channels_redis-4.2.1.tar.gz", hash = "sha256:8375e81493e684792efe6e6eca60ef3d7782ef76c6664057d2e5c31e80d636dd"},
+]
+
+[package.dependencies]
+asgiref = ">=3.2.10,<4"
+channels = "*"
+msgpack = ">=1.0,<2.0"
+redis = ">=4.6"
+
+[package.extras]
+cryptography = ["cryptography (>=1.3.0)"]
+tests = ["async-timeout", "cryptography (>=1.3.0)", "pytest", "pytest-asyncio", "pytest-timeout"]
+
[[package]]
name = "charset-normalizer"
version = "3.3.2"
@@ -628,6 +712,17 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+[[package]]
+name = "constantly"
+version = "23.10.4"
+description = "Symbolic constants in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "constantly-23.10.4-py3-none-any.whl", hash = "sha256:3fd9b4d1c3dc1ec9757f3c52aef7e53ad9323dbe39f51dfd4c43853b68dfa3f9"},
+ {file = "constantly-23.10.4.tar.gz", hash = "sha256:aa92b70a33e2ac0bb33cd745eb61776594dc48764b06c35e0efd050b7f1c7cbd"},
+]
+
[[package]]
name = "coverage"
version = "7.6.1"
@@ -775,6 +870,25 @@ ssh = ["bcrypt (>=3.1.5)"]
test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
test-randomorder = ["pytest-randomly"]
+[[package]]
+name = "daphne"
+version = "4.1.2"
+description = "Django ASGI (HTTP/WebSocket) server"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "daphne-4.1.2-py3-none-any.whl", hash = "sha256:618d1322bb4d875342b99dd2a10da2d9aae7ee3645f765965fdc1e658ea5290a"},
+ {file = "daphne-4.1.2.tar.gz", hash = "sha256:fcbcace38eb86624ae247c7ffdc8ac12f155d7d19eafac4247381896d6f33761"},
+]
+
+[package.dependencies]
+asgiref = ">=3.5.2,<4"
+autobahn = ">=22.4.2"
+twisted = {version = ">=22.4", extras = ["tls"]}
+
+[package.extras]
+tests = ["django", "hypothesis", "pytest", "pytest-asyncio"]
+
[[package]]
name = "dataclasses-json"
version = "0.6.7"
@@ -1031,6 +1145,26 @@ files = [
Django = ">=3.2"
minio = ">=7.2.8"
+[[package]]
+name = "django-ninja"
+version = "1.3.0"
+description = "Django Ninja - Fast Django REST framework"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "django_ninja-1.3.0-py3-none-any.whl", hash = "sha256:f58096b6c767d1403dfd6c49743f82d780d7b9688d9302ecab316ac1fa6131bb"},
+ {file = "django_ninja-1.3.0.tar.gz", hash = "sha256:5b320e2dc0f41a6032bfa7e1ebc33559ae1e911a426f0c6be6674a50b20819be"},
+]
+
+[package.dependencies]
+Django = ">=3.1"
+pydantic = ">=2.0,<3.0.0"
+
+[package.extras]
+dev = ["pre-commit"]
+doc = ["markdown-include", "mkdocs", "mkdocs-material", "mkdocstrings"]
+test = ["django-stubs", "mypy (==1.7.1)", "psycopg2-binary", "pytest", "pytest-asyncio", "pytest-cov", "pytest-django", "ruff (==0.5.7)"]
+
[[package]]
name = "django-oauth-toolkit"
version = "2.4.0"
@@ -1060,6 +1194,20 @@ files = [
{file = "django_ordered_model-3.7.4-py3-none-any.whl", hash = "sha256:dfcd3183fe0749dad1c9971cba1d6240ce7328742a30ddc92feca41107bb241d"},
]
+[[package]]
+name = "django-polymorphic"
+version = "3.1.0"
+description = "Seamless polymorphic inheritance for Django models"
+optional = false
+python-versions = "*"
+files = [
+ {file = "django-polymorphic-3.1.0.tar.gz", hash = "sha256:d6955b5308bf6e41dcb22ba7c96f00b51dfa497a8a5ab1e9c06c7951bf417bf8"},
+ {file = "django_polymorphic-3.1.0-py3-none-any.whl", hash = "sha256:08bc4f4f4a773a19b2deced5a56deddd1ef56ebd15207bf4052e2901c25ef57e"},
+]
+
+[package.dependencies]
+Django = ">=2.1"
+
[[package]]
name = "django-prometheus"
version = "2.3.1"
@@ -1477,69 +1625,6 @@ files = [
{file = "funcy-2.0.tar.gz", hash = "sha256:3963315d59d41c6f30c04bc910e10ab50a3ac4a225868bfa96feed133df075cb"},
]
-[[package]]
-name = "gevent"
-version = "24.2.1"
-description = "Coroutine-based network library"
-optional = false
-python-versions = ">=3.8"
-files = [
- {file = "gevent-24.2.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f947a9abc1a129858391b3d9334c45041c08a0f23d14333d5b844b6e5c17a07"},
- {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde283313daf0b34a8d1bab30325f5cb0f4e11b5869dbe5bc61f8fe09a8f66f3"},
- {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1df555431f5cd5cc189a6ee3544d24f8c52f2529134685f1e878c4972ab026"},
- {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14532a67f7cb29fb055a0e9b39f16b88ed22c66b96641df8c04bdc38c26b9ea5"},
- {file = "gevent-24.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd23df885318391856415e20acfd51a985cba6919f0be78ed89f5db9ff3a31cb"},
- {file = "gevent-24.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:ca80b121bbec76d7794fcb45e65a7eca660a76cc1a104ed439cdbd7df5f0b060"},
- {file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b9913c45d1be52d7a5db0c63977eebb51f68a2d5e6fd922d1d9b5e5fd758cc98"},
- {file = "gevent-24.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:918cdf8751b24986f915d743225ad6b702f83e1106e08a63b736e3a4c6ead789"},
- {file = "gevent-24.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d5325ccfadfd3dcf72ff88a92fb8fc0b56cacc7225f0f4b6dcf186c1a6eeabc"},
- {file = "gevent-24.2.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5"},
- {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8bb35ce57a63c9a6896c71a285818a3922d8ca05d150fd1fe49a7f57287b836"},
- {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7f87c2c02e03d99b95cfa6f7a776409083a9e4d468912e18c7680437b29222c"},
- {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968581d1717bbcf170758580f5f97a2925854943c45a19be4d47299507db2eb7"},
- {file = "gevent-24.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7899a38d0ae7e817e99adb217f586d0a4620e315e4de577444ebeeed2c5729be"},
- {file = "gevent-24.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:f5e8e8d60e18d5f7fd49983f0c4696deeddaf6e608fbab33397671e2fcc6cc91"},
- {file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fbfdce91239fe306772faab57597186710d5699213f4df099d1612da7320d682"},
- {file = "gevent-24.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cdf66977a976d6a3cfb006afdf825d1482f84f7b81179db33941f2fc9673bb1d"},
- {file = "gevent-24.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:1dffb395e500613e0452b9503153f8f7ba587c67dd4a85fc7cd7aa7430cb02cc"},
- {file = "gevent-24.2.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:6c47ae7d1174617b3509f5d884935e788f325eb8f1a7efc95d295c68d83cce40"},
- {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7cac622e11b4253ac4536a654fe221249065d9a69feb6cdcd4d9af3503602e0"},
- {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf5b9c72b884c6f0c4ed26ef204ee1f768b9437330422492c319470954bc4cc7"},
- {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f5de3c676e57177b38857f6e3cdfbe8f38d1cd754b63200c0615eaa31f514b4f"},
- {file = "gevent-24.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4faf846ed132fd7ebfbbf4fde588a62d21faa0faa06e6f468b7faa6f436b661"},
- {file = "gevent-24.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:368a277bd9278ddb0fde308e6a43f544222d76ed0c4166e0d9f6b036586819d9"},
- {file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f8a04cf0c5b7139bc6368b461257d4a757ea2fe89b3773e494d235b7dd51119f"},
- {file = "gevent-24.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9d8d0642c63d453179058abc4143e30718b19a85cbf58c2744c9a63f06a1d388"},
- {file = "gevent-24.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:94138682e68ec197db42ad7442d3cf9b328069c3ad8e4e5022e6b5cd3e7ffae5"},
- {file = "gevent-24.2.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:8f4b8e777d39013595a7740b4463e61b1cfe5f462f1b609b28fbc1e4c4ff01e5"},
- {file = "gevent-24.2.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:141a2b24ad14f7b9576965c0c84927fc85f824a9bb19f6ec1e61e845d87c9cd8"},
- {file = "gevent-24.2.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:9202f22ef811053077d01f43cc02b4aaf4472792f9fd0f5081b0b05c926cca19"},
- {file = "gevent-24.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2955eea9c44c842c626feebf4459c42ce168685aa99594e049d03bedf53c2800"},
- {file = "gevent-24.2.1-cp38-cp38-win32.whl", hash = "sha256:44098038d5e2749b0784aabb27f1fcbb3f43edebedf64d0af0d26955611be8d6"},
- {file = "gevent-24.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:117e5837bc74a1673605fb53f8bfe22feb6e5afa411f524c835b2ddf768db0de"},
- {file = "gevent-24.2.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:2ae3a25ecce0a5b0cd0808ab716bfca180230112bb4bc89b46ae0061d62d4afe"},
- {file = "gevent-24.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7ceb59986456ce851160867ce4929edaffbd2f069ae25717150199f8e1548b8"},
- {file = "gevent-24.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2e9ac06f225b696cdedbb22f9e805e2dd87bf82e8fa5e17756f94e88a9d37cf7"},
- {file = "gevent-24.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:90cbac1ec05b305a1b90ede61ef73126afdeb5a804ae04480d6da12c56378df1"},
- {file = "gevent-24.2.1-cp39-cp39-win32.whl", hash = "sha256:782a771424fe74bc7e75c228a1da671578c2ba4ddb2ca09b8f959abdf787331e"},
- {file = "gevent-24.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:3adfb96637f44010be8abd1b5e73b5070f851b817a0b182e601202f20fa06533"},
- {file = "gevent-24.2.1-pp310-pypy310_pp73-macosx_11_0_universal2.whl", hash = "sha256:7b00f8c9065de3ad226f7979154a7b27f3b9151c8055c162332369262fc025d8"},
- {file = "gevent-24.2.1.tar.gz", hash = "sha256:432fc76f680acf7cf188c2ee0f5d3ab73b63c1f03114c7cd8a34cebbe5aa2056"},
-]
-
-[package.dependencies]
-cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""}
-greenlet = {version = ">=3.0rc3", markers = "platform_python_implementation == \"CPython\" and python_version >= \"3.11\""}
-"zope.event" = "*"
-"zope.interface" = "*"
-
-[package.extras]
-dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"]
-docs = ["furo", "repoze.sphinx.autointerface", "sphinx", "sphinxcontrib-programoutput", "zope.schema"]
-monitor = ["psutil (>=5.7.0)"]
-recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)"]
-test = ["cffi (>=1.12.2)", "coverage (>=5.0)", "dnspython (>=1.16.0,<2.0)", "idna", "objgraph", "psutil (>=5.7.0)", "requests"]
-
[[package]]
name = "google-ai-generativelanguage"
version = "0.4.0"
@@ -1809,28 +1894,6 @@ googleapis-common-protos = ">=1.5.5"
grpcio = ">=1.62.3"
protobuf = ">=4.21.6"
-[[package]]
-name = "gunicorn"
-version = "23.0.0"
-description = "WSGI HTTP Server for UNIX"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
- {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
-]
-
-[package.dependencies]
-gevent = {version = ">=1.4.0", optional = true, markers = "extra == \"gevent\""}
-packaging = "*"
-
-[package.extras]
-eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
-gevent = ["gevent (>=1.4.0)"]
-setproctitle = ["setproctitle"]
-testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
-tornado = ["tornado (>=0.2)"]
-
[[package]]
name = "h11"
version = "0.14.0"
@@ -1889,15 +1952,70 @@ http2 = ["h2 (>=3,<5)"]
socks = ["socksio (==1.*)"]
trio = ["trio (>=0.22.0,<0.26.0)"]
+[[package]]
+name = "httptools"
+version = "0.6.4"
+description = "A collection of framework independent HTTP protocol utils."
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"},
+ {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"},
+ {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"},
+ {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"},
+ {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"},
+ {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"},
+ {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"},
+ {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"},
+ {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"},
+ {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"},
+ {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"},
+ {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"},
+ {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"},
+ {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"},
+ {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"},
+ {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"},
+ {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"},
+ {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"},
+ {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"},
+ {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"},
+ {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"},
+ {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"},
+ {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"},
+ {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"},
+ {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"},
+ {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"},
+ {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"},
+ {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"},
+ {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"},
+ {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"},
+ {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"},
+ {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"},
+ {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"},
+ {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"},
+ {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"},
+ {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"},
+ {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"},
+ {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"},
+ {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"},
+ {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"},
+ {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"},
+ {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"},
+ {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"},
+]
+
+[package.extras]
+test = ["Cython (>=0.29.24)"]
+
[[package]]
name = "httpx"
-version = "0.27.2"
+version = "0.28.1"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
files = [
- {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
- {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
+ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
+ {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
]
[package.dependencies]
@@ -1905,7 +2023,6 @@ anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
-sniffio = "*"
[package.extras]
brotli = ["brotli", "brotlicffi"]
@@ -1936,6 +2053,20 @@ files = [
{file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"},
]
+[[package]]
+name = "hyperlink"
+version = "21.0.0"
+description = "A featureful, immutable, and correct URL for Python."
+optional = false
+python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"},
+ {file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"},
+]
+
+[package.dependencies]
+idna = ">=2.5"
+
[[package]]
name = "idna"
version = "3.10"
@@ -1950,6 +2081,23 @@ files = [
[package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
+[[package]]
+name = "incremental"
+version = "24.7.2"
+description = "A small library that versions your Python projects."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "incremental-24.7.2-py3-none-any.whl", hash = "sha256:8cb2c3431530bec48ad70513931a760f446ad6c25e8333ca5d95e24b0ed7b8fe"},
+ {file = "incremental-24.7.2.tar.gz", hash = "sha256:fb4f1d47ee60efe87d4f6f0ebb5f70b9760db2b2574c59c8e8912be4ebd464c9"},
+]
+
+[package.dependencies]
+setuptools = ">=61.0"
+
+[package.extras]
+scripts = ["click (>=6.0)"]
+
[[package]]
name = "inflection"
version = "0.5.1"
@@ -2539,6 +2687,79 @@ pycryptodome = "*"
typing-extensions = "*"
urllib3 = "*"
+[[package]]
+name = "msgpack"
+version = "1.1.0"
+description = "MessagePack serializer"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"},
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"},
+ {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"},
+ {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"},
+ {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"},
+ {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"},
+ {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"},
+ {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"},
+ {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"},
+ {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"},
+ {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"},
+ {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"},
+ {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"},
+ {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"},
+ {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"},
+ {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"},
+ {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"},
+ {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"},
+ {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"},
+ {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"},
+ {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"},
+ {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"},
+ {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"},
+ {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"},
+ {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"},
+ {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"},
+ {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"},
+ {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"},
+ {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"},
+ {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"},
+ {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"},
+ {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"},
+]
+
[[package]]
name = "multidict"
version = "6.1.0"
@@ -3379,6 +3600,24 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
+[[package]]
+name = "pyopenssl"
+version = "24.3.0"
+description = "Python wrapper module around the OpenSSL library"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pyOpenSSL-24.3.0-py3-none-any.whl", hash = "sha256:e474f5a473cd7f92221cc04976e48f4d11502804657a08a989fb3be5514c904a"},
+ {file = "pyopenssl-24.3.0.tar.gz", hash = "sha256:49f7a019577d834746bc55c5fce6ecbcec0f2b4ec5ce1cf43a9a173b8138bb36"},
+]
+
+[package.dependencies]
+cryptography = ">=41.0.5,<45"
+
+[package.extras]
+docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx_rtd_theme"]
+test = ["pretend", "pytest (>=3.0.1)", "pytest-rerunfailures"]
+
[[package]]
name = "pypdf2"
version = "3.0.1"
@@ -4075,6 +4314,30 @@ starlette = ["starlette (>=0.19.1)"]
starlite = ["starlite (>=1.48)"]
tornado = ["tornado (>=6)"]
+[[package]]
+name = "service-identity"
+version = "24.2.0"
+description = "Service identity verification for pyOpenSSL & cryptography."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "service_identity-24.2.0-py3-none-any.whl", hash = "sha256:6b047fbd8a84fd0bb0d55ebce4031e400562b9196e1e0d3e0fe2b8a59f6d4a85"},
+ {file = "service_identity-24.2.0.tar.gz", hash = "sha256:b8683ba13f0d39c6cd5d625d2c5f65421d6d707b013b375c355751557cbe8e09"},
+]
+
+[package.dependencies]
+attrs = ">=19.1.0"
+cryptography = "*"
+pyasn1 = "*"
+pyasn1-modules = "*"
+
+[package.extras]
+dev = ["coverage[toml] (>=5.0.2)", "idna", "mypy", "pyopenssl", "pytest", "types-pyopenssl"]
+docs = ["furo", "myst-parser", "pyopenssl", "sphinx", "sphinx-notfound-page"]
+idna = ["idna"]
+mypy = ["idna", "mypy", "types-pyopenssl"]
+tests = ["coverage[toml] (>=5.0.2)", "pytest"]
+
[[package]]
name = "setuptools"
version = "75.1.0"
@@ -4403,6 +4666,60 @@ notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
+[[package]]
+name = "twisted"
+version = "24.11.0"
+description = "An asynchronous networking framework written in Python"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "twisted-24.11.0-py3-none-any.whl", hash = "sha256:fe403076c71f04d5d2d789a755b687c5637ec3bcd3b2b8252d76f2ba65f54261"},
+ {file = "twisted-24.11.0.tar.gz", hash = "sha256:695d0556d5ec579dcc464d2856b634880ed1319f45b10d19043f2b57eb0115b5"},
+]
+
+[package.dependencies]
+attrs = ">=22.2.0"
+automat = ">=24.8.0"
+constantly = ">=15.1"
+hyperlink = ">=17.1.1"
+idna = {version = ">=2.4", optional = true, markers = "extra == \"tls\""}
+incremental = ">=24.7.0"
+pyopenssl = {version = ">=21.0.0", optional = true, markers = "extra == \"tls\""}
+service-identity = {version = ">=18.1.0", optional = true, markers = "extra == \"tls\""}
+typing-extensions = ">=4.2.0"
+zope-interface = ">=5"
+
+[package.extras]
+all-non-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.2,<5.0)", "h2 (>=3.2,<5.0)", "httpx[http2] (>=0.27)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"]
+conch = ["appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)"]
+dev = ["coverage (>=7.5,<8.0)", "cython-test-exception-raiser (>=1.0.2,<2)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "pydoctor (>=23.9.0,<23.10.0)", "pyflakes (>=2.2,<3.0)", "pyhamcrest (>=2)", "python-subunit (>=1.4,<2.0)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "twistedchecker (>=0.7,<1.0)"]
+dev-release = ["pydoctor (>=23.9.0,<23.10.0)", "pydoctor (>=23.9.0,<23.10.0)", "sphinx (>=6,<7)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "towncrier (>=23.6,<24.0)"]
+gtk-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.2,<5.0)", "h2 (>=3.2,<5.0)", "httpx[http2] (>=0.27)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pygobject", "pygobject", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"]
+http2 = ["h2 (>=3.2,<5.0)", "priority (>=1.1.0,<2.0)"]
+macos-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.2,<5.0)", "h2 (>=3.2,<5.0)", "httpx[http2] (>=0.27)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"]
+mypy = ["appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "coverage (>=7.5,<8.0)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.2,<5.0)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "idna (>=2.4)", "mypy (==1.10.1)", "mypy-zope (==1.0.6)", "priority (>=1.1.0,<2.0)", "pydoctor (>=23.9.0,<23.10.0)", "pyflakes (>=2.2,<3.0)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "python-subunit (>=1.4,<2.0)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "sphinx (>=6,<7)", "sphinx-rtd-theme (>=1.3,<2.0)", "towncrier (>=23.6,<24.0)", "twistedchecker (>=0.7,<1.0)", "types-pyopenssl", "types-setuptools"]
+osx-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.2,<5.0)", "h2 (>=3.2,<5.0)", "httpx[http2] (>=0.27)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyobjc-core", "pyobjc-core", "pyobjc-framework-cfnetwork", "pyobjc-framework-cfnetwork", "pyobjc-framework-cocoa", "pyobjc-framework-cocoa", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)"]
+serial = ["pyserial (>=3.0)", "pywin32 (!=226)"]
+test = ["cython-test-exception-raiser (>=1.0.2,<2)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "pyhamcrest (>=2)"]
+tls = ["idna (>=2.4)", "pyopenssl (>=21.0.0)", "service-identity (>=18.1.0)"]
+windows-platform = ["appdirs (>=1.4.0)", "appdirs (>=1.4.0)", "bcrypt (>=3.1.3)", "bcrypt (>=3.1.3)", "cryptography (>=3.3)", "cryptography (>=3.3)", "cython-test-exception-raiser (>=1.0.2,<2)", "cython-test-exception-raiser (>=1.0.2,<2)", "h2 (>=3.2,<5.0)", "h2 (>=3.2,<5.0)", "httpx[http2] (>=0.27)", "httpx[http2] (>=0.27)", "hypothesis (>=6.56)", "hypothesis (>=6.56)", "idna (>=2.4)", "idna (>=2.4)", "priority (>=1.1.0,<2.0)", "priority (>=1.1.0,<2.0)", "pyhamcrest (>=2)", "pyhamcrest (>=2)", "pyopenssl (>=21.0.0)", "pyopenssl (>=21.0.0)", "pyserial (>=3.0)", "pyserial (>=3.0)", "pywin32 (!=226)", "pywin32 (!=226)", "pywin32 (!=226)", "pywin32 (!=226)", "service-identity (>=18.1.0)", "service-identity (>=18.1.0)", "twisted-iocpsupport (>=1.0.2)", "twisted-iocpsupport (>=1.0.2)"]
+
+[[package]]
+name = "txaio"
+version = "23.1.1"
+description = "Compatibility API between asyncio/Twisted/Trollius"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "txaio-23.1.1-py2.py3-none-any.whl", hash = "sha256:aaea42f8aad50e0ecfb976130ada140797e9dcb85fad2cf72b0f37f8cefcb490"},
+ {file = "txaio-23.1.1.tar.gz", hash = "sha256:f9a9216e976e5e3246dfd112ad7ad55ca915606b60b84a757ac769bd404ff704"},
+]
+
+[package.extras]
+all = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"]
+dev = ["pep8 (>=1.6.2)", "pyenchant (>=1.6.6)", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "sphinx (>=1.2.3)", "sphinx-rtd-theme (>=0.1.9)", "sphinxcontrib-spelling (>=2.1.2)", "tox (>=2.1.1)", "tox-gh-actions (>=2.2.0)", "twine (>=1.6.5)", "wheel"]
+twisted = ["twisted (>=20.3.0)", "zope.interface (>=5.2.0)"]
+
[[package]]
name = "types-pillow"
version = "10.2.0.20240822"
@@ -4526,6 +4843,75 @@ h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
+[[package]]
+name = "uvicorn"
+version = "0.34.0"
+description = "The lightning-fast ASGI server."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"},
+ {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+h11 = ">=0.8"
+
+[package.extras]
+standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[[package]]
+name = "uvloop"
+version = "0.21.0"
+description = "Fast implementation of asyncio event loop on top of libuv"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"},
+ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"},
+ {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"},
+ {file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"},
+ {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"},
+ {file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"},
+ {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"},
+ {file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"},
+ {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"},
+ {file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"},
+ {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"},
+ {file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"},
+ {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"},
+ {file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"},
+ {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"},
+ {file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"},
+ {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"},
+ {file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"},
+ {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"},
+ {file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"},
+ {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"},
+ {file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"},
+ {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"},
+ {file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"},
+ {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"},
+ {file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"},
+ {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"},
+ {file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"},
+ {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"},
+ {file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"},
+ {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"},
+ {file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"},
+ {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"},
+ {file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"},
+ {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"},
+ {file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"},
+ {file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"},
+]
+
+[package.extras]
+dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
+
[[package]]
name = "vine"
version = "5.1.0"
@@ -4673,6 +5059,20 @@ files = [
{file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"},
]
+[[package]]
+name = "wsproto"
+version = "1.2.0"
+description = "WebSockets state-machine based protocol implementation"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
+ {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
+]
+
+[package.dependencies]
+h11 = ">=0.9.0,<1"
+
[[package]]
name = "yarl"
version = "1.11.1"
@@ -4795,24 +5195,6 @@ netaddr = "*"
requests = "*"
urllib3 = "*"
-[[package]]
-name = "zope-event"
-version = "5.0"
-description = "Very basic event publishing system"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "zope.event-5.0-py3-none-any.whl", hash = "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26"},
- {file = "zope.event-5.0.tar.gz", hash = "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd"},
-]
-
-[package.dependencies]
-setuptools = "*"
-
-[package.extras]
-docs = ["Sphinx"]
-test = ["zope.testrunner"]
-
[[package]]
name = "zope-interface"
version = "7.0.3"
@@ -4867,4 +5249,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
-content-hash = "ffc7fdebf88ee475f6017c11fd65edf29f20b6f7e929cc960c8675037f7f4af9"
+content-hash = "d1fcec7cfd139999f2d9db729e5e19981e8cf17f50cdbdd20552f0eba4e9f268"
@@ -17,7 +17,6 @@ djangorestframework-simplejwt = "^5.2.2"
djangorestframework = "^3.14.0"
yookassa = "^2.4.0"
environs = "^9.5.0"
-gunicorn = {extras = ["gevent"], version = "^23.0.0"}
dj-rest-auth = "^4.0.1"
django-celery-beat = "^2.5.0"
minio = "^7.1.15"
@@ -48,6 +47,15 @@ django-prometheus = "^2.3.1"
psycopg2-binary = "^2.9.10"
filetype = "^1.2.0"
sentry-sdk = {extras = ["django"], version = "^2.19.0"}
+django-polymorphic = "^3.1.0"
+httpx = "^0.28.1"
+django-ninja = "^1.3.0"
+channels = {extras = ["daphne"], version = "^4.2.0"}
+uvicorn = "^0.34.0"
+httptools = "^0.6.4"
+uvloop = "^0.21.0"
+wsproto = "^1.2.0"
+channels-redis = "^4.2.1"
[tool.poetry.group.test.dependencies]