@@ -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)]), + } +) @@ -180,6 +180,23 @@ 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), + ) + ], + 'capacity': 1500, + 'expiry': 20, + }, + }, +} # Database DATABASES = { @@ -190,10 +207,16 @@ DATABASES = { 'PASSWORD': env.str('POSTGRES_PASSWORD'), 'HOST': env.str('POSTGRES_HOST'), 'PORT': env.str('POSTGRES_PORT'), - 'POOL_OPTIONS': {'POOL_SIZE': 3, 'MAX_OVERFLOW': 6, 'RECYCLE': 60 * 5}, } } +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,7 +1,9 @@ +import itertools import time from datetime import timedelta from decimal import Decimal -from io import BytesIO +from io import BufferedReader, BytesIO +from typing import Any, Dict, Generator, List, Optional from django.core.files.uploadedfile import InMemoryUploadedFile from langchain import hub @@ -16,6 +18,7 @@ from PIL import Image from backend import settings from messages.models import Message +from ml_model.constants import TEMPORARY_TEST_TEXT from ml_model.models import ModelCategory, ModelInput, ModelParameter, ModelVersion from ml_model.services.base import SimpleService from payments.exceptions.insufficient_balance import InsufficientBalance @@ -313,3 +316,24 @@ 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]] = [], + ) -> str | Generator[str, None, None]: + # with httpx.Client() as client: + # data = {} + # if not stream: + # client.post('https://openai.com/api/chat/completions', json=data) + # resp = client.post( + # 'https://openai.com/api/chat/completions', json=data | {'stream': True} + # ) + for chunk in itertools.batched(TEMPORARY_TEST_TEXT, 10): + time.sleep(0.3) + yield ''.join(chunk) @@ -0,0 +1,10 @@ +TEMPORARY_TEST_TEXT = """ +

Lorem ipsum

+dolor sit amet, consectetur adipiscing elit. Proin venenatis ornare porta. Vivamus laoreet, eros vitae consectetur semper, nisi purus dignissim libero, et aliquam nisi sem eu nibh. Praesent malesuada augue eget purus interdum congue. Vestibulum ut maximus dolor. Donec vitae enim et mi ultrices faucibus. Vestibulum quis sollicitudin arcu, eget molestie sapien. Pellentesque id eros erat. Vestibulum vitae dapibus diam, non ornare eros. Maecenas gravida vulputate mi, id auctor sapien faucibus in. + +

Aenean aliquet finibus lobortis.

+Nam convallis mi a luctus laoreet. Etiam suscipit egestas posuere. Aenean eget facilisis augue, ut pulvinar lacus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc pharetra nisl non nulla tristique gravida. Phasellus nunc eros, cursus in nibh a, suscipit vehicula justo. Sed in mi in nulla efficitur faucibus. Maecenas at vestibulum nunc, ultricies elementum sapien. Proin pulvinar ultrices placerat. Duis convallis lobortis pellentesque. Donec ornare ante mauris, a consectetur magna lobortis consequat. Quisque gravida eget neque eleifend bibendum. + +Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Donec rutrum viverra suscipit. Etiam nec laoreet quam. Aenean in ligula sit amet nibh consectetur porta. Cras molestie nisl a odio lacinia, ac ornare odio hendrerit. Mauris et pulvinar dolor. Morbi pretium luctus sem, ac congue eros ornare eget. Pellentesque laoreet elementum felis id mollis. Maecenas velit nulla, gravida sodales lectus in, fermentum auctor justo. Sed euismod maximus ex, vel pharetra massa ultricies vel. Curabitur interdum cursus quam ut placerat. Phasellus eleifend purus sed nisi pellentesque condimentum. Fusce finibus, felis quis mattis tempus, tortor elit tincidunt massa, non interdum tellus neque id mauris. Morbi dapibus volutpat lectus sed pellentesque. Curabitur sagittis lacinia quam eu viverra. +In sit amet nunc sed urna aliquet vehicula id vel justo. Duis vel massa eleifend, tristique ante in, ullamcorper tellus. Donec egestas lacus eu libero sodales luctus. Duis id maximus arcu, non dictum felis. Cras nisl odio, viverra in arcu at, sagittis gravida augue. Aliquam quis metus vel urna finibus sodales. Praesent a porttitor magna. Integer eu est ac ligula luctus consequat. +""" @@ -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,22 @@ +# Generated by Django 5.0.9 on 2024-12-22 16:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('copywrite', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='copywrite', + name='starred', + ), + migrations.AddField( + model_name='copywrite', + name='favourite', + field=models.BooleanField(default=False, verbose_name='В избранном'), + ), + ] @@ -0,0 +1,113 @@ +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 django.template import Context, Template + +from authentication.models.user import CustomUserModel +from core.typing import PrimitiveType +from ml_model.services.chatgpt import Chatgpt +from tools.copywrite.models import ( + Copywrite, + OverridenVariable, + SelfCopywrite, + TemplateCopywrite, +) + + +class CopywriteService: + @classmethod + def list_copywrites_by_user( + cls, + user: CustomUserModel, + filters: Q = Q(), + ordering: Literal['favourite'] = 'favourite', + ) -> QuerySet[Copywrite]: + return ( + Copywrite.objects.filter(user=user) + .filter(filters) + .prefetch_related( + '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) -> TemplateCopywrite | SelfCopywrite: + return Copywrite.objects.prefetch_related('polymorphic_ctype').get( + id=copywrite_id + ) + + @classmethod + def mark_as_favourite(cls, copywrite_id: UUID) -> None: + Copywrite.objects.filter(id=copywrite_id).update(favourite=True) + + @classmethod + def set_output(cls, copywrite_id: UUID, output_content: str) -> None: + Copywrite.objects.filter(id=copywrite_id).update(output_content=output_content) + + @classmethod + def update_input_content(cls, copywrite_id: UUID, input_content: str) -> None: + SelfCopywrite.objects.filter(id=copywrite_id).update(input_content=input_content) + + @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, copywrite_id: UUID, variable_id: UUID, value: PrimitiveType + ) -> OverridenVariable: + ov = OverridenVariable.objects.get( + copywrite__id=copywrite_id, variable__id=variable_id + ) + ov.value = value + ov.save() + return ov + + @classmethod + def remove_overriden_variable(cls, copywrite_id: UUID, variable_id: UUID) -> None: + OverridenVariable.objects.get( + copywrite__id=copywrite_id, variable__id=variable_id + ).delete() + + @classmethod + def generate(cls, copywrite_id: UUID) -> Generator[str, None, None]: + cp: SelfCopywrite | TemplateCopywrite = Copywrite.objects.prefetch_related( + 'polymorphic_ctype' + ).get(id=copywrite_id) + if isinstance(cp, SelfCopywrite): + content = cp.input_content or '' + elif isinstance(cp, TemplateCopywrite): + overriden_vars = { + overriden_var.variable.id: overriden_var.value + for overriden_var in cp.overriden_variables + } + blueprint = Template(cp.template.content) + ctx = { + var.sysname: active_val + for var in cp.template.variables + if (active_val := overriden_vars.get(var.id) or var.default_value) + } + content = blueprint.render(Context(ctx)) + else: + raise NotImplementedError + for chunk in Chatgpt.evaluate(content=content, stream=True): + yield chunk @@ -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(self, event_data): + return await self.send_json(event_data) @@ -0,0 +1 @@ +class CopywriteAlreadyGenerated(Exception): ... @@ -1,84 +1,192 @@ -from django.contrib.postgres.fields import ArrayField +from typing import Optional +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 ordered_model.models import OrderedModel +from polymorphic.models import PolymorphicManager, PolymorphicModel -from messages.models import SingleStore - - -def template_picture_upload(instance: 'Template', filename: str): - return filename - - -class Copywrite(SingleStore): - created_at = models.DateTimeField(default=timezone.now, verbose_name='Когда создан', editable=False) - - @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'] +def template_picture_uploader(instance: 'Template', filename: str): + return f'{instance.title}/{filename}' -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='Исходящий промпт' + ) + favourite = 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) -> bool: + return not (self.output_content is None or self.output_content == '') + + @property + def draft(self) -> bool: + return not self.generated + + @property + def label(self) -> Optional[str]: + if self.output_content and len(self.output_content) > 30: + return f'{self.output_content[:20]}...' + return self.output_content + + class Meta: + verbose_name = 'Копирайт' + verbose_name_plural = 'Копирайты' + + +class SelfCopywrite(Copywrite): + input_content: str = 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,146 @@ +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 tools.copywrite.consumers import CopywriteConsumer +from tools.copywrite.exceptions import CopywriteAlreadyGenerated +from tools.copywrite.schemas import ( + CopywriteFilterSchema, + CreateDraftSchema, + CreateOverridenVariableSchema, + OverridenVariableSchema, + SelfCopywriteSchema, + SelfCopywriteShortSchema, + TemplateCategorySchema, + TemplateCopywriteSchema, + TemplateCopywriteShortSchema, + TemplateFilterSchema, + TemplateSchema, + TemplateShortSchema, + UpdateOverridenVariableSchema, + UpdateSelfCopywriteSchema, +) +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[TemplateCopywriteShortSchema | SelfCopywriteShortSchema], +) +def list_copywrites( + request, + filters: CopywriteFilterSchema = Query(...), + ordering: Literal['favourite'] = 'favourite', +): + 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): + cp = CopywriteService.get_copywrite_by_id(copywrite_id=id) + if cp.generated: + raise CopywriteAlreadyGenerated + generate_task.delay(copywrite_id=id) + + +@router.put('copywrites/{id}/', tags=['copywrite/copywrites'], response={204: None}) +def mark_as_favourite(request, id: UUID): + return CopywriteService.mark_as_favourite(copywrite_id=id) + + +@router.patch('copywrites/{id}/', tags=['copywrite/copywrites'], response={204: None}) +def update_input_content(request, id: UUID, data: UpdateSelfCopywriteSchema): + return CopywriteService.update_input_content(copywrite_id=id, **data.model_dump()) + + +@router.post( + 'copywrites/{copywrite_id}/variables/', + tags=['copywrite/variables'], + response=OverridenVariableSchema, +) +def override_variable(request, copywrite_id: UUID, data: CreateOverridenVariableSchema): + return CopywriteService.override_variable( + copywrite_id=copywrite_id, **data.model_dump() + ) + + +@router.put( + 'copywrites/{copywrite_id}/variables/{id}/', + tags=['copywrite/variables'], + response={204: None}, +) +def update_overriden_variable( + request, copywrite_id: UUID, id: UUID, data: UpdateOverridenVariableSchema +): + return CopywriteService.update_overriden_variable( + copywrite_id=copywrite_id, variable_id=id, **data.model_dump() + ) + + +@router.delete( + 'copywrites/{copywrite_id}/variables/{id}/', + tags=['copywrite/variables'], + response={204: None}, +) +def remove_overriden_variable(request, copywrite_id: UUID, id: UUID): + CopywriteService.remove_overriden_variable(copywrite_id=copywrite_id, 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,176 @@ +from typing import Any, List, Literal, Optional +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 UpdateSelfCopywriteSchema(ModelSchema): + class Meta: + model = SelfCopywrite + exclude = ( + 'id', + 'favourite', + 'created_at', + 'copywrite_ptr', + 'polymorphic_ctype', + 'output_content', + 'user', + ) + fields_optional = '__all__' + + +class UpdateTemplateCopywriteSchema(ModelSchema): + class Meta: + model = SelfCopywrite + exclude = ( + 'id', + 'favourite', + 'created_at', + 'copywrite_ptr', + 'polymorphic_ctype', + 'output_content', + 'user', + ) + fields_optional = '__all__' + + +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): + draft: bool + label: Optional[str] = None + + class Meta: + model = SelfCopywrite + exclude = ( + 'copywrite_ptr', + 'user', + 'polymorphic_ctype', + 'created_at', + ) + + +class SelfCopywriteShortSchema(BaseCopywriteSchema, ModelSchema): + draft: bool + label: Optional[str] = None + + class Meta: + model = SelfCopywrite + exclude = ( + 'copywrite_ptr', + 'user', + 'polymorphic_ctype', + 'created_at', + 'output_content', + 'input_content', + ) + + +class OverridenVariableSchema(ModelSchema): + class Meta: + model = OverridenVariable + fields = ('id', 'variable', 'value') + + +class CreateOverridenVariableSchema(ModelSchema): + variable_id: UUID + + class Meta: + model = OverridenVariable + exclude = ('id', 'copywrite', 'variable') + + +class TemplateCopywriteSchema(BaseCopywriteSchema, ModelSchema): + template: TemplateSchema + overriden_variables: List[OverridenVariableSchema] + draft: bool + label: Optional[str] = None + + class Meta: + model = TemplateCopywrite + exclude = ( + 'copywrite_ptr', + 'user', + 'polymorphic_ctype', + 'created_at', + ) + + +class TemplateCopywriteShortSchema(BaseCopywriteSchema, ModelSchema): + template: TemplateShortSchema + draft: bool + label: Optional[str] = None + + class Meta: + model = TemplateCopywrite + exclude = ( + 'copywrite_ptr', + 'user', + 'polymorphic_ctype', + 'created_at', + 'output_content', + ) + + +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,26 @@ +from uuid import UUID + +from asgiref.sync import async_to_sync +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): + content = '' + layer: RedisChannelLayer = get_channel_layer() + for chunk in CopywriteService.generate(copywrite_id=copywrite_id): + async_to_sync(layer.group_send)( + COPYWRITE_WS_KEY % copywrite_id, + {'type': 'generate.chunk', 'event_data': {'chunk': chunk, 'end': False}}, + ) + content += chunk + async_to_sync(layer.group_send)( + COPYWRITE_WS_KEY % copywrite_id, + {'type': 'generate.chunk', 'event_data': {'chunk': '', 'end': True}}, + ) + CopywriteService.set_output(copywrite_id=copywrite_id, output_content=content) @@ -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.27.0" 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.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, + {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, ] [package.dependencies] @@ -1912,7 +2030,6 @@ brotli = ["brotli", "brotlicffi"] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] -zstd = ["zstandard (>=0.18.0)"] [[package]] name = "httpx-sse" @@ -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 = "ba8a23585f6180cfdf0ab22be662f922cb2531a22dd96256f4ad032594298e03" @@ -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.27" +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]