diff --git a/README.md b/README.md index 8d42790..60d42ca 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,39 @@ on local start can be found at http://localhost/gpt/api/docs prod docs https://bot.mywistr.ru/gpt/api/docs/ + +## Create migrations + +Init alembic + + alembic init alembic + + +```bash +cd bot_microservice +alembic revision --autogenerate -m 'create_quads_table' +alembic upgrade head +``` + + +Create table in alembic versions + +```bash +alembic --config ./alembic.ini revision -m "create account table" +alembic --config ./alembic.ini revision --autogenerate -m 'create_quads_table' +``` + + + +Run migrations + +```bash +cd ./bot_microservice # alembic root +alembic --config ./alembic.ini upgrade head +alembic --config ./alembic.ini downgrade 389018a3e0f0 +``` + + ## Help article [Следить за обновлениями этого репозитория](https://github.com/fantasy-peak/cpp-freegpt-webui) @@ -99,10 +132,12 @@ prod docs https://bot.mywistr.ru/gpt/api/docs/ ## TODO -- [] add Database and models -- [] add alembic migrations +- [x] add Database and models +- [x] add alembic migrations - [] add models priority and their rotation -- [] add more tests +- [x] add update model priority endpoint +- [] add more tests for gpt model selection +- [] add authorisation for api - [] reformat conftest.py file - [x] Add sentry - [x] Add graylog integration and availability to log to file diff --git a/bot_microservice/alembic.ini b/bot_microservice/alembic.ini new file mode 100644 index 0000000..70deaad --- /dev/null +++ b/bot_microservice/alembic.ini @@ -0,0 +1,52 @@ +[alembic] +script_location = infra/database/migrations +file_template = %%(year)d-%%(month).2d-%%(day).2d-%%(hour).2d-%%(minute).2d_%%(rev)s +prepend_sys_path = . +output_encoding = utf-8 + +[post_write_hooks] +hooks = black,autoflake,isort + +black.type = console_scripts +black.entrypoint = black + +autoflake.type = console_scripts +autoflake.entrypoint = autoflake + +isort.type = console_scripts +isort.entrypoint = isort + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/bot_microservice/api/bot/controllers.py b/bot_microservice/api/bot/controllers.py index d599f59..64482e8 100644 --- a/bot_microservice/api/bot/controllers.py +++ b/bot_microservice/api/bot/controllers.py @@ -1,7 +1,17 @@ -from fastapi import APIRouter, Request +from fastapi import APIRouter, Body, Depends, Path from starlette import status -from starlette.responses import Response +from starlette.responses import JSONResponse, Response +from telegram import Update +from api.bot.serializers import ( + ChatGptModelSerializer, + ChatGptModelsPrioritySerializer, + GETChatGptModelsSerializer, + LightChatGptModel, +) +from api.deps import get_bot_queue, get_chatgpt_service, get_update_from_request +from core.bot.app import BotQueue +from core.bot.services import ChatGptService from settings.config import settings router = APIRouter() @@ -15,5 +25,74 @@ router = APIRouter() summary="process bot updates", include_in_schema=False, ) -async def process_bot_updates(request: Request) -> None: - await request.app.state.queue.put_updates_on_queue(request) +async def process_bot_updates( + tg_update: Update = Depends(get_update_from_request), + queue: BotQueue = Depends(get_bot_queue), +) -> None: + await queue.put_updates_on_queue(tg_update) + + +@router.get( + "/models", + name="bot:models_list", + response_class=JSONResponse, + response_model=list[ChatGptModelSerializer], + status_code=status.HTTP_200_OK, + summary="list of models", +) +async def models_list( + chatgpt_service: ChatGptService = Depends(get_chatgpt_service), +) -> JSONResponse: + """Получить список всех моделей""" + models = await chatgpt_service.get_chatgpt_models() + return JSONResponse( + content=GETChatGptModelsSerializer(data=models).model_dump(), status_code=status.HTTP_200_OK # type: ignore + ) + + +@router.post( + "/models/{model_id}/priority", + name="bot:change_model_priority", + response_class=Response, + status_code=status.HTTP_202_ACCEPTED, + summary="change gpt model priority", +) +async def change_model_priority( + model_id: int = Path(..., gt=0, description="Id модели для обновления приореитета"), + chatgpt_service: ChatGptService = Depends(get_chatgpt_service), + gpt_model: ChatGptModelsPrioritySerializer = Body(...), +) -> None: + """Изменить приоритет модели в выдаче""" + await chatgpt_service.change_chatgpt_model_priority(model_id=model_id, priority=gpt_model.priority) + + +@router.post( + "/models", + name="bot:add_new_model", + response_model=ChatGptModelSerializer, + status_code=status.HTTP_201_CREATED, + summary="add new model", +) +async def add_new_model( + chatgpt_service: ChatGptService = Depends(get_chatgpt_service), + gpt_model: LightChatGptModel = Body(...), +) -> JSONResponse: + """Добавить новую модель""" + model = await chatgpt_service.add_chatgpt_model(gpt_model=gpt_model.model, priority=gpt_model.priority) + + return JSONResponse(content=model, status_code=status.HTTP_201_CREATED) + + +@router.delete( + "/models/{model_id}", + name="bot:delete_gpt_model", + response_class=Response, + status_code=status.HTTP_204_NO_CONTENT, + summary="delete gpt model", +) +async def delete_model( + model_id: int = Path(..., gt=0, description="Id модели для удаления"), + chatgpt_service: ChatGptService = Depends(get_chatgpt_service), +) -> None: + """Удалить gpt модель""" + await chatgpt_service.delete_chatgpt_model(model_id=model_id) diff --git a/bot_microservice/api/bot/deps.py b/bot_microservice/api/bot/deps.py deleted file mode 100644 index 2f35f83..0000000 --- a/bot_microservice/api/bot/deps.py +++ /dev/null @@ -1,13 +0,0 @@ -from fastapi import Depends -from starlette.requests import Request - -from core.bot.services import ChatGptService -from settings.config import AppSettings - - -def get_settings(request: Request) -> AppSettings: - return request.app.state.settings - - -def get_chat_gpt_service(settings: AppSettings = Depends(get_settings)) -> ChatGptService: - return ChatGptService(settings.GPT_MODEL) diff --git a/bot_microservice/api/bot/serializers.py b/bot_microservice/api/bot/serializers.py new file mode 100644 index 0000000..4d9ebaa --- /dev/null +++ b/bot_microservice/api/bot/serializers.py @@ -0,0 +1,24 @@ +from pydantic import BaseModel, ConfigDict, Field + + +class LightChatGptModel(BaseModel): + model: str = Field(..., title="Chat Gpt model") + priority: int = Field(default=0, ge=0, title="Приоритет модели") + + +class ChatGptModelsPrioritySerializer(BaseModel): + priority: int = Field(default=0, ge=0, title="Приоритет модели") + + +class ChatGptModelSerializer(BaseModel): + id: int = Field(..., gt=0, title="Id модели") + model: str = Field(..., title="Chat Gpt model") + priority: int = Field(..., ge=0, title="Приоритет модели") + + model_config = ConfigDict(from_attributes=True) + + +class GETChatGptModelsSerializer(BaseModel): + data: list[ChatGptModelSerializer] = Field(..., title="Список всех моделей") + + model_config = ConfigDict(from_attributes=True) diff --git a/bot_microservice/api/deps.py b/bot_microservice/api/deps.py new file mode 100644 index 0000000..1a637a4 --- /dev/null +++ b/bot_microservice/api/deps.py @@ -0,0 +1,50 @@ +from fastapi import Depends +from starlette.requests import Request +from telegram import Update + +from core.bot.app import BotApplication, BotQueue +from core.bot.repository import ChatGPTRepository +from core.bot.services import ChatGptService, SpeechToTextService +from infra.database.db_adapter import Database +from settings.config import AppSettings + + +def get_settings(request: Request) -> AppSettings: + return request.app.state.settings + + +def get_bot_app(request: Request) -> BotApplication: + return request.app.state.bot_app + + +def get_bot_queue(request: Request) -> BotQueue: + return request.app.state.queue + + +async def get_update_from_request(request: Request, bot_app: BotApplication = Depends(get_bot_app)) -> Update | None: + data = await request.json() + return Update.de_json(data, bot_app.bot) + + +def get_database(settings: AppSettings = Depends(get_settings)) -> Database: + return Database(settings=settings) + + +def get_chat_gpt_repository( + db: Database = Depends(get_database), settings: AppSettings = Depends(get_settings) +) -> ChatGPTRepository: + return ChatGPTRepository(settings=settings, db=db) + + +def get_speech_to_text_service() -> SpeechToTextService: + return SpeechToTextService() + + +def new_bot_queue(bot_app: BotApplication = Depends(get_bot_app)) -> BotQueue: + return BotQueue(bot_app=bot_app) + + +def get_chatgpt_service( + chat_gpt_repository: ChatGPTRepository = Depends(get_chat_gpt_repository), +) -> ChatGptService: + return ChatGptService(repository=chat_gpt_repository) diff --git a/bot_microservice/api/system/controllers.py b/bot_microservice/api/system/controllers.py index 7b0ae3a..900cee3 100644 --- a/bot_microservice/api/system/controllers.py +++ b/bot_microservice/api/system/controllers.py @@ -3,7 +3,7 @@ from fastapi.responses import ORJSONResponse from starlette import status from starlette.responses import Response -from api.bot.deps import get_chat_gpt_service +from api.deps import get_chatgpt_service from api.exceptions import BaseAPIException from constants import INVALID_GPT_REQUEST_MESSAGES from core.bot.services import ChatGptService @@ -33,12 +33,11 @@ async def healthcheck() -> ORJSONResponse: ) async def gpt_healthcheck( response: Response, - chatgpt_service: ChatGptService = Depends(get_chat_gpt_service), + chatgpt_service: ChatGptService = Depends(get_chatgpt_service), ) -> Response: - data = chatgpt_service.build_request_data("Привет!") response.status_code = status.HTTP_200_OK try: - chatgpt_response = await chatgpt_service.do_request(data) + chatgpt_response = await chatgpt_service.request_to_chatgpt_microservice(question="Привет!") if chatgpt_response.status_code != status.HTTP_200_OK: response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR for message in INVALID_GPT_REQUEST_MESSAGES: diff --git a/bot_microservice/constants.py b/bot_microservice/constants.py index 5e8a4c2..e68c497 100644 --- a/bot_microservice/constants.py +++ b/bot_microservice/constants.py @@ -1,4 +1,4 @@ -from enum import StrEnum +from enum import StrEnum, unique AUDIO_SEGMENT_DURATION = 120 * 1000 @@ -26,3 +26,35 @@ class LogLevelEnum(StrEnum): INFO = "info" DEBUG = "debug" NOTSET = "" + + +@unique +class ChatGptModelsEnum(StrEnum): + gpt_3_5_turbo_stream_openai = "gpt-3.5-turbo-stream-openai" + gpt_3_5_turbo_Aichat = "gpt-3.5-turbo-Aichat" + gpt_4_ChatgptAi = "gpt-4-ChatgptAi" + gpt_3_5_turbo_weWordle = "gpt-3.5-turbo-weWordle" + gpt_3_5_turbo_acytoo = "gpt-3.5-turbo-acytoo" + gpt_3_5_turbo_stream_DeepAi = "gpt-3.5-turbo-stream-DeepAi" + gpt_3_5_turbo_stream_H2o = "gpt-3.5-turbo-stream-H2o" + gpt_3_5_turbo_stream_yqcloud = "gpt-3.5-turbo-stream-yqcloud" + gpt_OpenAssistant_stream_HuggingChat = "gpt-OpenAssistant-stream-HuggingChat" + gpt_4_turbo_stream_you = "gpt-4-turbo-stream-you" + gpt_3_5_turbo_AItianhu = "gpt-3.5-turbo-AItianhu" + gpt_3_stream_binjie = "gpt-3-stream-binjie" + gpt_3_5_turbo_stream_CodeLinkAva = "gpt-3.5-turbo-stream-CodeLinkAva" + gpt_4_stream_ChatBase = "gpt-4-stream-ChatBase" + gpt_3_5_turbo_stream_aivvm = "gpt-3.5-turbo-stream-aivvm" + gpt_3_5_turbo_16k_stream_Ylokh = "gpt-3.5-turbo-16k-stream-Ylokh" + gpt_3_5_turbo_stream_Vitalentum = "gpt-3.5-turbo-stream-Vitalentum" + gpt_3_5_turbo_stream_GptGo = "gpt-3.5-turbo-stream-GptGo" + gpt_3_5_turbo_stream_AItianhuSpace = "gpt-3.5-turbo-stream-AItianhuSpace" + gpt_3_5_turbo_stream_Aibn = "gpt-3.5-turbo-stream-Aibn" + gpt_3_5_turbo_ChatgptDuo = "gpt-3.5-turbo-ChatgptDuo" + gpt_3_5_turbo_stream_FreeGpt = "gpt-3.5-turbo-stream-FreeGpt" + gpt_3_5_turbo_stream_ChatForAi = "gpt-3.5-turbo-stream-ChatForAi" + gpt_3_5_turbo_stream_Cromicle = "gpt-3.5-turbo-stream-Cromicle" + + @classmethod + def values(cls) -> set[str]: + return set(map(str, set(ChatGptModelsEnum))) diff --git a/bot_microservice/core/bot/app.py b/bot_microservice/core/bot/app.py index de96c9d..a5c6c96 100644 --- a/bot_microservice/core/bot/app.py +++ b/bot_microservice/core/bot/app.py @@ -6,7 +6,7 @@ from functools import cached_property from http import HTTPStatus from typing import Any -from fastapi import Request, Response +from fastapi import Response from loguru import logger from telegram import Bot, Update from telegram.ext import Application @@ -68,14 +68,11 @@ class BotQueue: bot_app: BotApplication queue: Queue = asyncio.Queue() # type: ignore[type-arg] - async def put_updates_on_queue(self, request: Request) -> Response: + async def put_updates_on_queue(self, tg_update: Update) -> Response: """ Listen /{URL_PREFIX}/{API_PREFIX}/{TELEGRAM_WEB_TOKEN} path and proxy post request to bot """ - data = await request.json() - tg_update = Update.de_json(data=data, bot=self.bot_app.application.bot) self.queue.put_nowait(tg_update) - return Response(status_code=HTTPStatus.ACCEPTED) async def get_updates_from_queue(self) -> None: diff --git a/bot_microservice/core/bot/commands.py b/bot_microservice/core/bot/commands.py index 1bce780..c903681 100644 --- a/bot_microservice/core/bot/commands.py +++ b/bot_microservice/core/bot/commands.py @@ -67,7 +67,7 @@ async def ask_question(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No await update.message.reply_text("Пожалуйста подождите, ответ в среднем занимает 10-15 секунд") - chat_gpt_service = ChatGptService(chat_gpt_model=settings.GPT_MODEL) + chat_gpt_service = ChatGptService.build() logger.warning("question asked", user=update.message.from_user, question=update.message.text) answer = await chat_gpt_service.request_to_chatgpt(question=update.message.text) await update.message.reply_text(answer) @@ -87,9 +87,9 @@ async def voice_recognize(update: Update, context: ContextTypes.DEFAULT_TYPE) -> logger.info("file has been saved", filename=tmpfile.name) - speech_to_text_service = SpeechToTextService(filename=tmpfile.name) + speech_to_text_service = SpeechToTextService() - speech_to_text_service.get_text_from_audio() + speech_to_text_service.get_text_from_audio(filename=tmpfile.name) part = 0 while speech_to_text_service.text_parts or not speech_to_text_service.text_recognised: diff --git a/bot_microservice/core/bot/models/__init__.py b/bot_microservice/core/bot/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot_microservice/core/bot/models/chat_gpt.py b/bot_microservice/core/bot/models/chat_gpt.py new file mode 100644 index 0000000..24f9967 --- /dev/null +++ b/bot_microservice/core/bot/models/chat_gpt.py @@ -0,0 +1,12 @@ +from sqlalchemy import INTEGER, SMALLINT, VARCHAR +from sqlalchemy.orm import Mapped, mapped_column + +from infra.database.base import Base + +__slots__ = ("ChatGpt",) + + +class ChatGpt(Base): + id: Mapped[int] = mapped_column("id", INTEGER(), primary_key=True, autoincrement=True) + model: Mapped[str] = mapped_column("model", VARCHAR(length=256), nullable=False, unique=True) + priority: Mapped[int] = mapped_column("priority", SMALLINT(), default=0) diff --git a/bot_microservice/core/bot/repository.py b/bot_microservice/core/bot/repository.py new file mode 100644 index 0000000..0a83555 --- /dev/null +++ b/bot_microservice/core/bot/repository.py @@ -0,0 +1,106 @@ +import random +from dataclasses import dataclass +from typing import Any, Sequence +from uuid import uuid4 + +import httpx +from httpx import AsyncClient, AsyncHTTPTransport, Response +from loguru import logger +from sqlalchemy import delete, desc, select, update +from sqlalchemy.dialects.sqlite import insert + +from constants import CHAT_GPT_BASE_URI, INVALID_GPT_REQUEST_MESSAGES +from core.bot.models.chat_gpt import ChatGpt +from infra.database.db_adapter import Database +from settings.config import AppSettings + + +@dataclass +class ChatGPTRepository: + settings: AppSettings + db: Database + + async def get_chatgpt_models(self) -> Sequence[ChatGpt]: + query = select(ChatGpt).order_by(desc(ChatGpt.priority)) + + async with self.db.session() as session: + result = await session.execute(query) + return result.scalars().all() + + async def change_chatgpt_model_priority(self, model_id: int, priority: int) -> None: + current_model = await self.get_current_chatgpt_model() + + reset_priority_query = update(ChatGpt).values(priority=0).filter(ChatGpt.model == current_model) + set_new_priority_query = update(ChatGpt).values(priority=priority).filter(ChatGpt.model == model_id) + + async with self.db.get_transaction_session() as session: + await session.execute(reset_priority_query) + await session.execute(set_new_priority_query) + + async def add_chatgpt_model(self, model: str, priority: int) -> dict[str, str | int]: + query = ( + insert(ChatGpt) + .values( + {ChatGpt.model: model, ChatGpt.priority: priority}, + ) + .prefix_with("OR IGNORE") + ) + async with self.db.session() as session: + await session.execute(query) + await session.commit() + return {"model": model, "priority": priority} + + async def delete_chatgpt_model(self, model_id: int) -> None: + query = delete(ChatGpt).filter_by(id=model_id) + + async with self.db.session() as session: + await session.execute(query) + + async def get_current_chatgpt_model(self) -> str: + query = select(ChatGpt.model).order_by(desc(ChatGpt.priority)).limit(1) + + async with self.db.session() as session: + result = await session.execute(query) + return result.scalar_one() + + async def ask_question(self, question: str, chat_gpt_model: str) -> str: + try: + response = await self.request_to_chatgpt_microservice(question=question, chat_gpt_model=chat_gpt_model) + status = response.status_code + for message in INVALID_GPT_REQUEST_MESSAGES: + if message in response.text: + message = f"{message}: {chat_gpt_model}" + logger.info(message, question=question, chat_gpt_model=chat_gpt_model) + return message + if status != httpx.codes.OK: + logger.info(f"got response status: {status} from chat api", response.text) + return "Что-то пошло не так, попробуйте еще раз или обратитесь к администратору" + return response.text + except Exception as error: + logger.error("error get data from chat api", error=error) + return "Вообще всё сломалось :(" + + async def request_to_chatgpt_microservice(self, question: str, chat_gpt_model: str) -> Response: + data = self._build_request_data(question=question, chat_gpt_model=chat_gpt_model) + + transport = AsyncHTTPTransport(retries=3) + async with AsyncClient(base_url=self.settings.GPT_BASE_HOST, transport=transport, timeout=50) as client: + return await client.post(CHAT_GPT_BASE_URI, json=data, timeout=50) + + @staticmethod + def _build_request_data(*, question: str, chat_gpt_model: str) -> dict[str, Any]: + return { + "conversation_id": str(uuid4()), + "action": "_ask", + "model": chat_gpt_model, + "jailbreak": "default", + "meta": { + "id": random.randint(10**18, 10**19 - 1), # noqa: S311 + "content": { + "conversation": [], + "internet_access": False, + "content_type": "text", + "parts": [{"content": question, "role": "user"}], + }, + }, + } diff --git a/bot_microservice/core/bot/services.py b/bot_microservice/core/bot/services.py index 46e17ce..a520662 100644 --- a/bot_microservice/core/bot/services.py +++ b/bot_microservice/core/bot/services.py @@ -1,12 +1,10 @@ import os -import random import subprocess # noqa from concurrent.futures.thread import ThreadPoolExecutor -from typing import Any -from uuid import uuid4 +from dataclasses import dataclass +from typing import Any, Sequence -import httpx -from httpx import AsyncClient, AsyncHTTPTransport, Response +from httpx import Response from loguru import logger from pydub import AudioSegment from speech_recognition import ( @@ -15,33 +13,30 @@ from speech_recognition import ( UnknownValueError as SpeechRecognizerError, ) -from constants import ( - AUDIO_SEGMENT_DURATION, - CHAT_GPT_BASE_URI, - INVALID_GPT_REQUEST_MESSAGES, -) +from constants import AUDIO_SEGMENT_DURATION +from core.bot.models.chat_gpt import ChatGpt +from core.bot.repository import ChatGPTRepository +from infra.database.db_adapter import Database from settings.config import settings class SpeechToTextService: - def __init__(self, filename: str) -> None: + def __init__(self) -> None: self.executor = ThreadPoolExecutor() - - self.filename = filename self.recognizer = Recognizer() self.recognizer.energy_threshold = 50 self.text_parts: dict[int, str] = {} self.text_recognised = False - def get_text_from_audio(self) -> None: - self.executor.submit(self.worker) + def get_text_from_audio(self, filename: str) -> None: + self.executor.submit(self.worker, filename=filename) - def worker(self) -> Any: - self._convert_file_to_wav() - self._convert_audio_to_text() + def worker(self, filename: str) -> Any: + self._convert_file_to_wav(filename) + self._convert_audio_to_text(filename) - def _convert_audio_to_text(self) -> None: - wav_filename = f"{self.filename}.wav" + def _convert_audio_to_text(self, filename: str) -> None: + wav_filename = f"{filename}.wav" speech = AudioSegment.from_wav(wav_filename) speech_duration = len(speech) @@ -63,18 +58,19 @@ class SpeechToTextService: # clean temp voice message main files try: os.remove(wav_filename) - os.remove(self.filename) + os.remove(filename) except FileNotFoundError as error: - logger.error("error temps files not deleted", error=error, filenames=[self.filename, self.filename]) + logger.error("error temps files not deleted", error=error, filenames=[filename, wav_filename]) - def _convert_file_to_wav(self) -> None: - new_filename = self.filename + ".wav" - cmd = ["ffmpeg", "-loglevel", "quiet", "-i", self.filename, "-vn", new_filename] + @staticmethod + def _convert_file_to_wav(filename: str) -> None: + new_filename = filename + ".wav" + cmd = ["ffmpeg", "-loglevel", "quiet", "-i", filename, "-vn", new_filename] try: subprocess.run(args=cmd) # noqa: S603 logger.info("file has been converted to wav", filename=new_filename) except Exception as error: - logger.error("cant convert voice", error=error, filename=self.filename) + logger.error("cant convert voice", error=error, filename=filename) def _recognize_by_google(self, filename: str, sound_segment: AudioSegment) -> str: tmp_filename = f"{filename}_tmp_part" @@ -91,48 +87,36 @@ class SpeechToTextService: raise error +@dataclass class ChatGptService: - def __init__(self, chat_gpt_model: str) -> None: - self.chat_gpt_model = chat_gpt_model + repository: ChatGPTRepository + + async def get_chatgpt_models(self) -> Sequence[ChatGpt]: + return await self.repository.get_chatgpt_models() async def request_to_chatgpt(self, question: str | None) -> str: question = question or "Привет!" - chat_gpt_request = self.build_request_data(question) - try: - response = await self.do_request(chat_gpt_request) - status = response.status_code - for message in INVALID_GPT_REQUEST_MESSAGES: - if message in response.text: - message = f"{message}: {settings.GPT_MODEL}" - logger.info(message, data=chat_gpt_request) - return message - if status != httpx.codes.OK: - logger.info(f"got response status: {status} from chat api", data=chat_gpt_request) - return "Что-то пошло не так, попробуйте еще раз или обратитесь к администратору" - return response.text - except Exception as error: - logger.error("error get data from chat api", error=error) - return "Вообще всё сломалось :(" + chat_gpt_model = await self.get_current_chatgpt_model() + return await self.repository.ask_question(question=question, chat_gpt_model=chat_gpt_model) - @staticmethod - async def do_request(data: dict[str, Any]) -> Response: - transport = AsyncHTTPTransport(retries=3) - async with AsyncClient(base_url=settings.GPT_BASE_HOST, transport=transport, timeout=50) as client: - return await client.post(CHAT_GPT_BASE_URI, json=data, timeout=50) + async def request_to_chatgpt_microservice(self, question: str) -> Response: + chat_gpt_model = await self.get_current_chatgpt_model() + return await self.repository.request_to_chatgpt_microservice(question=question, chat_gpt_model=chat_gpt_model) - def build_request_data(self, question: str) -> dict[str, Any]: - return { - "conversation_id": str(uuid4()), - "action": "_ask", - "model": self.chat_gpt_model, - "jailbreak": "default", - "meta": { - "id": random.randint(10**18, 10**19 - 1), # noqa: S311 - "content": { - "conversation": [], - "internet_access": False, - "content_type": "text", - "parts": [{"content": question, "role": "user"}], - }, - }, - } + async def get_current_chatgpt_model(self) -> str: + return await self.repository.get_current_chatgpt_model() + + async def change_chatgpt_model_priority(self, model_id: int, priority: int) -> None: + return await self.repository.change_chatgpt_model_priority(model_id=model_id, priority=priority) + + async def add_chatgpt_model(self, gpt_model: str, priority: int) -> dict[str, str | int]: + return await self.repository.add_chatgpt_model(model=gpt_model, priority=priority) + + async def delete_chatgpt_model(self, model_id: int) -> None: + return await self.repository.delete_chatgpt_model(model_id=model_id) + + @classmethod + def build(cls) -> "ChatGptService": + db = Database(settings=settings) + repository = ChatGPTRepository(settings=settings, db=db) + return ChatGptService(repository=repository) diff --git a/bot_microservice/core/lifetime.py b/bot_microservice/core/lifetime.py new file mode 100644 index 0000000..8be5a6a --- /dev/null +++ b/bot_microservice/core/lifetime.py @@ -0,0 +1,73 @@ +from asyncio import current_task +from typing import Awaitable, Callable + +from fastapi import FastAPI +from sqlalchemy.ext.asyncio import ( + AsyncSession, + async_scoped_session, + async_sessionmaker, + create_async_engine, +) + +from settings.config import AppSettings + + +def startup(app: FastAPI, settings: AppSettings) -> Callable[[], Awaitable[None]]: + """ + Actions to run on application startup. + + This function use fastAPI app to store data, + such as db_engine. + + :param app: the fastAPI application. + :param settings: app settings + :return: function that actually performs actions. + + """ + + async def _startup() -> None: + _setup_db(app, settings) + + return _startup + + +def shutdown(app: FastAPI) -> Callable[[], Awaitable[None]]: + """ + Actions to run on application's shutdown. + + :param app: fastAPI application. + :return: function that actually performs actions. + + """ + + async def _shutdown() -> None: + await app.state.db_engine.dispose() + + return _shutdown + + +def _setup_db(app: FastAPI, settings: AppSettings) -> None: + """ + Create connection to the database. + + This function creates SQLAlchemy engine instance, + session_factory for creating sessions + and stores them in the application's state property. + + :param app: fastAPI application. + """ + engine = create_async_engine( + str(settings.db_url), + echo=settings.DB_ECHO, + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + session_factory = async_scoped_session( + async_sessionmaker( + engine, + expire_on_commit=False, + class_=AsyncSession, + ), + scopefunc=current_task, + ) + app.state.db_engine = engine + app.state.db_session_factory = session_factory diff --git a/bot_microservice/core/utils.py b/bot_microservice/core/utils.py index 5f9243a..548642d 100644 --- a/bot_microservice/core/utils.py +++ b/bot_microservice/core/utils.py @@ -1,5 +1,6 @@ from datetime import datetime, timedelta from functools import lru_cache, wraps +from inspect import cleandoc from typing import Any @@ -22,3 +23,9 @@ def timed_cache(**timedelta_kwargs: Any) -> Any: return _wrapped return _wrapper + + +def clean_doc(cls: Any) -> str | None: + if cls.__doc__ is None: + return None + return cleandoc(cls.__doc__) diff --git a/bot_microservice/infra/database/__init__.py b/bot_microservice/infra/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot_microservice/infra/database/base.py b/bot_microservice/infra/database/base.py new file mode 100644 index 0000000..227e946 --- /dev/null +++ b/bot_microservice/infra/database/base.py @@ -0,0 +1,34 @@ +from sqlalchemy import Table, inspect +from sqlalchemy.orm import as_declarative, declared_attr + +from infra.database.meta import meta + + +@as_declarative(metadata=meta) +class Base: + """ + Base for all models. + + It has some type definitions to + enhance autocompletion. + """ + + # Generate __tablename__ automatically + @declared_attr + def __tablename__(self) -> str: + return self.__name__.lower() + + __table__: Table + + @classmethod + def get_real_column_name(cls, attr_name: str) -> str: + return getattr(inspect(cls).c, attr_name).name # type: ignore + + def __str__(self) -> str: + return self.__repr__() + + def __repr__(self) -> str: + try: + return f"{self.__class__.__name__}(id={self.id})" # type: ignore[attr-defined] + except AttributeError: + return super().__repr__() diff --git a/bot_microservice/infra/database/db_adapter.py b/bot_microservice/infra/database/db_adapter.py new file mode 100644 index 0000000..1d64181 --- /dev/null +++ b/bot_microservice/infra/database/db_adapter.py @@ -0,0 +1,102 @@ +import os +import pkgutil +from asyncio import current_task +from contextlib import asynccontextmanager +from pathlib import Path +from typing import AsyncGenerator + +from loguru import logger +from sqlalchemy.ext.asyncio import ( + AsyncEngine, + AsyncSession, + async_scoped_session, + async_sessionmaker, + create_async_engine, +) + +from settings.config import AppSettings + + +class Database: + def __init__(self, settings: AppSettings) -> None: + self.db_connect_url = settings.db_url + self.echo_logs = settings.DB_ECHO + self.db_file = settings.DB_FILE + self._engine: AsyncEngine = create_async_engine( + str(settings.db_url), + echo=settings.DB_ECHO, + execution_options={"isolation_level": "AUTOCOMMIT"}, + ) + self._async_session_factory = async_scoped_session( + async_sessionmaker( + autoflush=False, + class_=AsyncSession, + expire_on_commit=False, + bind=self._engine, + ), + scopefunc=current_task, + ) + + @asynccontextmanager + async def session(self) -> AsyncGenerator[AsyncSession, None]: + session: AsyncSession = self._async_session_factory() + + async with session: + try: + yield session + except Exception: + await session.rollback() + raise + + @asynccontextmanager + async def get_transaction_session(self) -> AsyncGenerator[AsyncSession, None]: + async with self._async_session_factory() as session, session.begin(): + try: + yield session + except Exception as error: + await session.rollback() + raise error + + async def create_database(self) -> None: + """ + Create a test database. + + :param engine: Async engine for database creation + :param db_path: path to sqlite file + + """ + if not self.db_file.exists(): + from infra.database.meta import meta + + load_all_models() + try: + async with self._engine.begin() as connection: + await connection.run_sync(meta.create_all) + + logger.info("all migrations are applied") + except Exception as err: + logger.error("Cant run migrations", err=err) + + async def drop_database(self) -> None: + """ + Drop current database. + + :param path: Delete sqlite database file + + """ + if self.db_file.exists(): + os.remove(self.db_file) + + +def load_all_models() -> None: + """Load all models from this folder.""" + package_dir = Path(__file__).resolve().parent.parent + package_dir = package_dir.joinpath("core") + modules = pkgutil.walk_packages(path=[str(package_dir)], prefix="core.") + models_packages = [module for module in modules if module.ispkg and "models" in module.name] + for module in models_packages: + model_pkgs = pkgutil.walk_packages( + path=[os.path.join(str(module.module_finder.path), "models")], prefix=f"{module.name}." # type: ignore + ) + for model_pkg in model_pkgs: + __import__(model_pkg.name) diff --git a/bot_microservice/infra/database/deps.py b/bot_microservice/infra/database/deps.py new file mode 100644 index 0000000..eecf86b --- /dev/null +++ b/bot_microservice/infra/database/deps.py @@ -0,0 +1,20 @@ +from typing import AsyncGenerator + +from sqlalchemy.ext.asyncio import AsyncSession +from starlette.requests import Request + + +async def get_db_session(request: Request) -> AsyncGenerator[AsyncSession, None]: + """ + Create and get database session. + + :param request: current request. + :yield: database session. + """ + session: AsyncSession = request.app.state.db_session_factory() + + try: + yield session + finally: + await session.commit() + await session.close() diff --git a/bot_microservice/infra/database/meta.py b/bot_microservice/infra/database/meta.py new file mode 100644 index 0000000..5d24aa1 --- /dev/null +++ b/bot_microservice/infra/database/meta.py @@ -0,0 +1,3 @@ +from sqlalchemy import MetaData + +meta = MetaData() diff --git a/bot_microservice/infra/database/migrations/__init__.py b/bot_microservice/infra/database/migrations/__init__.py new file mode 100644 index 0000000..6dccb95 --- /dev/null +++ b/bot_microservice/infra/database/migrations/__init__.py @@ -0,0 +1 @@ +"""Alembic migraions.""" diff --git a/bot_microservice/infra/database/migrations/env.py b/bot_microservice/infra/database/migrations/env.py new file mode 100644 index 0000000..b115747 --- /dev/null +++ b/bot_microservice/infra/database/migrations/env.py @@ -0,0 +1,81 @@ +import asyncio +from logging.config import fileConfig + +from alembic import context +from sqlalchemy.ext.asyncio.engine import create_async_engine +from sqlalchemy.future import Connection + +from infra.database.db_adapter import load_all_models +from infra.database.meta import meta +from settings.config import settings + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# for 'autogenerate' support from myapp import mymodel +load_all_models() + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# add your model's MetaData object here +target_metadata = meta + + +async def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + context.configure( + url=str(settings.db_url), + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def do_run_migrations(connection: Connection) -> None: + """ + Run actual sync migrations. + + :param connection: connection to the database. + + """ + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_migrations_online() -> None: + """ + Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = create_async_engine(str(settings.db_url)) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) + + +if context.is_offline_mode(): + asyncio.run(run_migrations_offline()) +else: + asyncio.run(run_migrations_online()) diff --git a/bot_microservice/infra/database/migrations/script.py.mako b/bot_microservice/infra/database/migrations/script.py.mako new file mode 100644 index 0000000..55df286 --- /dev/null +++ b/bot_microservice/infra/database/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade() -> None: + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + ${downgrades if downgrades else "pass"} diff --git a/bot_microservice/infra/database/migrations/versions/2023-10-05-18-28_eb78565abec7.py b/bot_microservice/infra/database/migrations/versions/2023-10-05-18-28_eb78565abec7.py new file mode 100644 index 0000000..a977a8b --- /dev/null +++ b/bot_microservice/infra/database/migrations/versions/2023-10-05-18-28_eb78565abec7.py @@ -0,0 +1,34 @@ +"""initial commit + +Revision ID: eb78565abec7 +Revises: +Create Date: 2023-10-05 18:28:30.915361 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "eb78565abec7" +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "chatgpt", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("model", sa.VARCHAR(length=256), nullable=False), + sa.Column("priority", sa.SMALLINT(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("model"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("chatgpt") + # ### end Alembic commands ### diff --git a/bot_microservice/infra/database/migrations/versions/2023-10-05-20-44_c2e443941930.py b/bot_microservice/infra/database/migrations/versions/2023-10-05-20-44_c2e443941930.py new file mode 100644 index 0000000..6af811b --- /dev/null +++ b/bot_microservice/infra/database/migrations/versions/2023-10-05-20-44_c2e443941930.py @@ -0,0 +1,49 @@ +"""create chat gpt models + +Revision ID: c2e443941930 +Revises: eb78565abec7 +Create Date: 2025-10-05 20:44:05.414977 + +""" + +from sqlalchemy import create_engine, select +from sqlalchemy.orm import sessionmaker + +from constants import ChatGptModelsEnum +from core.bot.models.chat_gpt import ChatGpt +from settings.config import settings + +# revision identifiers, used by Alembic. +revision = "c2e443941930" +down_revision = "eb78565abec7" +branch_labels: str | None = None +depends_on: str | None = None + +engine = create_engine(str(settings.db_url), echo=settings.DB_ECHO) +session_factory = sessionmaker(engine) + + +def upgrade() -> None: + with session_factory() as session: + query = select(ChatGpt) + results = session.execute(query) + models = results.scalars().all() + + if models: + return None + models = [] + for model in ChatGptModelsEnum: + priority = 0 if model != "gpt-3.5-turbo-stream-FreeGpt" else 1 + fields = {"model": model, "priority": priority} + models.append(ChatGpt(**fields)) + session.add_all(models) + session.commit() + + +def downgrade() -> None: + with session_factory() as session: + session.execute(f"""TRUNCATE TABLE {ChatGpt.__tablename__}""") + session.commit() + + +engine.dispose() diff --git a/bot_microservice/infra/database/migrations/versions/__init__.py b/bot_microservice/infra/database/migrations/versions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot_microservice/infra/logging_conf.py b/bot_microservice/infra/logging_conf.py index 789fe56..190f97d 100644 --- a/bot_microservice/infra/logging_conf.py +++ b/bot_microservice/infra/logging_conf.py @@ -17,6 +17,56 @@ else: Record = dict[str, Any] +class Formatter: + @staticmethod + def json_formatter(record: Record) -> str: + # Обрезаем `\n` в конце логов, т.к. в json формате переносы не нужны + return Formatter.scrap_sensitive_info(record.get("message", "").strip()) + + @staticmethod + def sentry_formatter(record: Record) -> str: + if message := record.get("message", ""): + record["message"] = Formatter.scrap_sensitive_info(message) + return "{name}:{function} {message}" + + @staticmethod + def text_formatter(record: Record) -> str: + # WARNING !!! + # Функция должна возвращать строку, которая содержит только шаблоны для форматирования. + # Если в строку прокидывать значения из record (или еще откуда-либо), + # то loguru может принять их за f-строки и попытается обработать, что приведет к ошибке. + # Например, если нужно достать какое-то значение из поля extra, вместо того чтобы прокидывать его в строку + # формата, нужно прокидывать подстроку вида {extra[тут_ключ]} + + if message := record.get("message", ""): + record["message"] = Formatter.scrap_sensitive_info(message) + + # Стандартный формат loguru. Задается через env LOGURU_FORMAT + format_ = ( + "{time:YYYY-MM-DD HH:mm:ss.SSS} | " + "{level: <8} | " + "{name}:{function}:{line} - " + "{message}" + ) + + # Добавляем мета параметры по типу user_id, art_id, которые передаются через logger.bind(...) + extra = record["extra"] + if extra: + formatted = ", ".join(f"{key}" + "={extra[" + str(key) + "]}" for key, value in extra.items()) + format_ += f" - {formatted}" + + format_ += "\n" + + if record["exception"] is not None: + format_ += "{exception}\n" + + return format_ + + @staticmethod + def scrap_sensitive_info(message: str) -> str: + return message.replace(settings.TELEGRAM_API_TOKEN, "TELEGRAM_API_TOKEN".center(24, "*")) + + class InterceptHandler(logging.Handler): def emit(self, record: logging.LogRecord) -> None: # Get corresponding Loguru level if it exists @@ -31,13 +81,9 @@ class InterceptHandler(logging.Handler): frame = cast(FrameType, frame.f_back) depth += 1 - logger.opt(depth=depth, exception=record.exc_info).log(level, self._scrap_sensitive_info(record)) - - @staticmethod - def _scrap_sensitive_info(record: logging.LogRecord) -> str: - message = record.getMessage() - message.replace(settings.TELEGRAM_API_TOKEN, "TELEGRAM_API_TOKEN".center(24, "*")) - return message + logger.opt(depth=depth, exception=record.exc_info).log( + level, Formatter.scrap_sensitive_info(record.getMessage()) + ) def configure_logging( @@ -45,7 +91,7 @@ def configure_logging( ) -> None: intercept_handler = InterceptHandler() - formatter = _json_formatter if enable_json_logs else _text_formatter + formatter = Formatter.json_formatter if enable_json_logs else Formatter.text_formatter base_config_handlers = [intercept_handler] @@ -64,10 +110,10 @@ def configure_logging( base_config_handlers.append(graylog_handler) loguru_handlers.append({**base_loguru_handler, "sink": graylog_handler}) if log_to_file: - file_path = os.path.join(DIR_LOGS, log_to_file) + file_path = DIR_LOGS / log_to_file if not os.path.exists(log_to_file): - with open(file_path, 'w') as f: - f.write('') + with open(file_path, "w") as f: + f.write("") loguru_handlers.append({**base_loguru_handler, "sink": file_path}) logging.basicConfig(handlers=base_config_handlers, level=level.name) @@ -78,42 +124,4 @@ def configure_logging( # https://forum.sentry.io/t/changing-issue-title-when-logging-with-traceback/446 if enable_sentry_logs: handler = EventHandler(level=logging.WARNING) - logger.add(handler, diagnose=True, level=logging.WARNING, format=_sentry_formatter) - - -def _json_formatter(record: Record) -> str: - # Обрезаем `\n` в конце логов, т.к. в json формате переносы не нужны - return record.get("message", "").strip() - - -def _sentry_formatter(record: Record) -> str: - return "{name}:{function} {message}" - - -def _text_formatter(record: Record) -> str: - # WARNING !!! - # Функция должна возвращать строку, которая содержит только шаблоны для форматирования. - # Если в строку прокидывать значения из record (или еще откуда-либо), - # то loguru может принять их за f-строки и попытается обработать, что приведет к ошибке. - # Например, если нужно достать какое-то значение из поля extra, вместо того чтобы прокидывать его в строку формата, - # нужно прокидывать подстроку вида {extra[тут_ключ]} - - # Стандартный формат loguru. Задается через env LOGURU_FORMAT - format_ = ( - "{time:YYYY-MM-DD HH:mm:ss.SSS} | " - "{level: <8} | " - "{name}:{function}:{line} - {message}" - ) - - # Добавляем мета параметры по типу user_id, art_id, которые передаются через logger.bind(...) - extra = record["extra"] - if extra: - formatted = ", ".join(f"{key}" + "={extra[" + str(key) + "]}" for key, value in extra.items()) - format_ += f" - {formatted}" - - format_ += "\n" - - if record["exception"] is not None: - format_ += "{exception}\n" - - return format_ + logger.add(handler, diagnose=True, level=logging.WARNING, format=Formatter.sentry_formatter) diff --git a/bot_microservice/main.py b/bot_microservice/main.py index 3e2e113..981fcce 100644 --- a/bot_microservice/main.py +++ b/bot_microservice/main.py @@ -8,6 +8,7 @@ from fastapi.responses import UJSONResponse from constants import LogLevelEnum from core.bot.app import BotApplication, BotQueue from core.bot.handlers import bot_event_handlers +from core.lifetime import shutdown, startup from infra.logging_conf import configure_logging from routers import api_router from settings.config import AppSettings, get_settings @@ -26,8 +27,12 @@ class Application: ) self.app.state.settings = settings self.app.state.queue = BotQueue(bot_app=bot_app) + self.app.state.bot_app = bot_app self.bot_app = bot_app + self.app.on_event("startup")(startup(self.app, settings)) + self.app.on_event("shutdown")(shutdown(self.app)) + self.app.include_router(api_router) self.configure_hooks() configure_logging( @@ -51,18 +56,18 @@ class Application: def configure_hooks(self) -> None: if self.bot_app.start_with_webhook: - self.app.add_event_handler("startup", self._on_start_up) + self.app.add_event_handler("startup", self._bot_start_up) else: self.app.add_event_handler("startup", self.bot_app.polling) - self.app.add_event_handler("shutdown", self._on_shutdown) + self.app.add_event_handler("shutdown", self._bot_shutdown) - async def _on_start_up(self) -> None: + async def _bot_start_up(self) -> None: await self.bot_app.set_webhook() loop = asyncio.get_event_loop() loop.create_task(self.app.state.queue.get_updates_from_queue()) - async def _on_shutdown(self) -> None: + async def _bot_shutdown(self) -> None: await asyncio.gather(self.bot_app.delete_webhook(), self.bot_app.shutdown()) diff --git a/bot_microservice/settings/config.py b/bot_microservice/settings/config.py index 820f869..4065af4 100644 --- a/bot_microservice/settings/config.py +++ b/bot_microservice/settings/config.py @@ -6,6 +6,7 @@ from typing import Any from dotenv import load_dotenv from pydantic import model_validator from pydantic_settings import BaseSettings +from yarl import URL from constants import API_PREFIX @@ -52,6 +53,9 @@ class AppSettings(SentrySettings, BaseSettings): DOMAIN: str = "https://localhost" URL_PREFIX: str = "" + DB_FILE: Path = SHARED_DIR / "chat_gpt.db" + DB_ECHO: bool = False + # ==== gpt settings ==== GPT_MODEL: str = "gpt-3.5-turbo-stream-DeepAi" GPT_BASE_HOST: str = "http://chat_service:8858" @@ -91,6 +95,18 @@ class AppSettings(SentrySettings, BaseSettings): def bot_webhook_url(self) -> str: return "/".join([self.api_prefix, self.token_part]) + @cached_property + def db_url(self) -> URL: + """ + Assemble database URL from settings. + + :return: database URL. + """ + return URL.build( + scheme="sqlite+aiosqlite", + path=f"///{self.DB_FILE}", + ) + class Config: case_sensitive = True diff --git a/chat_gpt_microservice/.gitignore b/chat_gpt_microservice/.gitignore index e5279d7..0247e19 100644 --- a/chat_gpt_microservice/.gitignore +++ b/chat_gpt_microservice/.gitignore @@ -154,4 +154,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/chat_gpt_microservice/include/free_gpt.h b/chat_gpt_microservice/include/free_gpt.h index b2b6309..3c55fe4 100644 --- a/chat_gpt_microservice/include/free_gpt.h +++ b/chat_gpt_microservice/include/free_gpt.h @@ -23,7 +23,6 @@ public: boost::asio::awaitable deepAi(std::shared_ptr, nlohmann::json); boost::asio::awaitable aiChat(std::shared_ptr, nlohmann::json); boost::asio::awaitable chatGptAi(std::shared_ptr, nlohmann::json); - boost::asio::awaitable weWordle(std::shared_ptr, nlohmann::json); boost::asio::awaitable acytoo(std::shared_ptr, nlohmann::json); boost::asio::awaitable openAi(std::shared_ptr, nlohmann::json); boost::asio::awaitable h2o(std::shared_ptr, nlohmann::json); @@ -31,7 +30,6 @@ public: boost::asio::awaitable huggingChat(std::shared_ptr, nlohmann::json); boost::asio::awaitable you(std::shared_ptr, nlohmann::json); boost::asio::awaitable binjie(std::shared_ptr, nlohmann::json); - boost::asio::awaitable codeLinkAva(std::shared_ptr, nlohmann::json); boost::asio::awaitable chatBase(std::shared_ptr, nlohmann::json); boost::asio::awaitable aivvm(std::shared_ptr, nlohmann::json); boost::asio::awaitable ylokh(std::shared_ptr, nlohmann::json); @@ -39,6 +37,9 @@ public: boost::asio::awaitable gptGo(std::shared_ptr, nlohmann::json); boost::asio::awaitable aibn(std::shared_ptr, nlohmann::json); boost::asio::awaitable chatGptDuo(std::shared_ptr, nlohmann::json); + boost::asio::awaitable chatForAi(std::shared_ptr, nlohmann::json); + boost::asio::awaitable freeGpt(std::shared_ptr, nlohmann::json); + boost::asio::awaitable cromicle(std::shared_ptr, nlohmann::json); private: boost::asio::awaitable, std::string>> diff --git a/chat_gpt_microservice/src/free_gpt.cpp b/chat_gpt_microservice/src/free_gpt.cpp index 2b97ddb..ea18a48 100644 --- a/chat_gpt_microservice/src/free_gpt.cpp +++ b/chat_gpt_microservice/src/free_gpt.cpp @@ -641,11 +641,15 @@ FreeGpt::createHttpClient(boost::asio::ssl::context& ctx, std::string_view host, } boost::asio::awaitable FreeGpt::deepAi(std::shared_ptr ch, nlohmann::json json) { - ScopeExit auto_exit{[&] { ch->close(); }}; + co_await boost::asio::post(boost::asio::bind_executor(*m_thread_pool_ptr, boost::asio::use_awaitable)); + boost::system::error_code err{}; + ScopeExit _exit{[=] { boost::asio::post(ch->get_executor(), [=] { ch->close(); }); }}; + auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); std::string user_agent{ - R"(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36)"}; + R"(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36)"}; + std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist(0, 100000000); @@ -654,19 +658,39 @@ boost::asio::awaitable FreeGpt::deepAi(std::shared_ptr ch, nlohma auto api_key = std::format("tryit-{}-{}", part1, part2); constexpr char CRLF[] = "\r\n"; - constexpr char MULTI_PART_BOUNDARY[] = "9bc627aea4f77e150e6057f78036e73f"; - constexpr std::string_view host{"api.deepai.org"}; - constexpr std::string_view port{"443"}; + static std::string MULTI_PART_BOUNDARY = "9bc627aea4f77e150e6057f78036e73f"; - boost::beast::http::request req{boost::beast::http::verb::post, - "/make_me_a_sandwich", 11}; - req.set(boost::beast::http::field::host, host); - req.set(boost::beast::http::field::user_agent, user_agent); - req.set("Api-Key", api_key); - req.set(boost::beast::http::field::content_type, - std::format("multipart/form-data; boundary={}", MULTI_PART_BOUNDARY)); + CURLcode res; + CURL* curl = curl_easy_init(); + if (!curl) { + auto error_info = std::format("curl_easy_init() failed:{}", curl_easy_strerror(res)); + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, error_info); + co_return; + } + curl_easy_setopt(curl, CURLOPT_URL, "https://api.deepai.org/hacking_is_a_crime"); + + if (!m_cfg.http_proxy.empty()) + curl_easy_setopt(curl, CURLOPT_PROXY, m_cfg.http_proxy.c_str()); + + struct Input { + std::shared_ptr ch; + std::string recv; + }; + Input input{ch}; + auto action_cb = [](void* contents, size_t size, size_t nmemb, void* userp) -> size_t { + boost::system::error_code err{}; + auto input_ptr = static_cast(userp); + std::string data{(char*)contents, size * nmemb}; + auto& [ch, recv] = *input_ptr; + boost::asio::post(ch->get_executor(), [=] { ch->try_send(err, data); }); + return size * nmemb; + }; + size_t (*action_fn)(void* contents, size_t size, size_t nmemb, void* userp) = action_cb; + curlEasySetopt(curl); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, action_fn); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &input); - auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); nlohmann::json request_json{{{"role", "user"}, {"content", std::move(prompt)}}}; std::ostringstream payload; @@ -674,30 +698,37 @@ boost::asio::awaitable FreeGpt::deepAi(std::shared_ptr ch, nlohma << CRLF << "chat" << CRLF << "--" << MULTI_PART_BOUNDARY << CRLF << R"(Content-Disposition: form-data; name="chatHistory")" << CRLF << CRLF << request_json.dump() << CRLF << "--" << MULTI_PART_BOUNDARY << "--" << CRLF; - SPDLOG_INFO("{}", payload.str()); - req.body() = payload.str(); - req.prepare_payload(); + auto str = payload.str(); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, str.c_str()); - int recreate_num{0}; -create_client: - boost::asio::ssl::context ctx(boost::asio::ssl::context::tls); - ctx.set_verify_mode(boost::asio::ssl::verify_none); - auto client = co_await createHttpClient(ctx, host, port); - if (!client.has_value()) { - SPDLOG_ERROR("createHttpClient: {}", client.error()); - co_await ch->async_send(err, client.error(), use_nothrow_awaitable); + struct curl_slist* headers = nullptr; + auto content_type_str = std::format("Content-Type: multipart/form-data; boundary={}", MULTI_PART_BOUNDARY); + SPDLOG_INFO("content_type_str: {}", content_type_str); + headers = curl_slist_append(headers, content_type_str.c_str()); + auto api_key_str = std::format("api-key: {}", api_key); + headers = curl_slist_append(headers, api_key_str.c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + ScopeExit auto_exit{[=] { + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + }}; + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + auto error_info = std::format("curl_easy_perform() failed:{}", curl_easy_strerror(res)); + ch->try_send(err, error_info); co_return; } - auto& stream_ = client.value(); - - auto ret = co_await sendRequestRecvChunk(ch, stream_, req, 200, [&ch](std::string recv_str) { - boost::system::error_code ec{}; - ch->try_send(ec, recv_str); - }); - if (ret == Status::Close && recreate_num == 0) { - recreate_num++; - goto create_client; + int32_t response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, std::format("deepai http code:{}", response_code)); + co_return; } co_return; } @@ -1007,116 +1038,6 @@ create_client: co_return; } -boost::asio::awaitable FreeGpt::weWordle(std::shared_ptr ch, nlohmann::json json) { - ScopeExit auto_exit{[&] { ch->close(); }}; - boost::system::error_code err{}; - - auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); - - auto random = [](int len) { - static std::string chars{"abcdefghijklmnopqrstuvwxyz0123456789"}; - static std::string letter{"abcdefghijklmnopqrstuvwxyz"}; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> dis(0, 1000000); - std::string random_string; - random_string += chars[dis(gen) % letter.length()]; - len = len - 1; - for (int i = 0; i < len; i++) - random_string += chars[dis(gen) % chars.length()]; - return random_string; - }; - auto user_id = random(16); - auto app_id = random(31); - auto now = std::chrono::time_point_cast(std::chrono::system_clock::now()); - auto request_date = std::format("{:%Y-%m-%dT%H:%M:%S.000Z}", now); - - constexpr std::string_view host = "wewordle.org"; - constexpr std::string_view port = "443"; - - boost::beast::http::request req{boost::beast::http::verb::post, - "/gptapi/v1/android/turbo", 11}; - req.set(boost::beast::http::field::host, host); - req.set("pragma", "no-cache"); - req.set("accept", "*/*"); - req.set( - boost::beast::http::field::user_agent, - R"(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36)"); - req.set(boost::beast::http::field::content_type, "application/json"); - req.set("Accept-Encoding", "gzip, deflate"); - - constexpr std::string_view json_str = R"({ - "user":"j1b892x978flimoa", - "messages":[ - { - "role":"user", - "content":"user: hello\nassistant:" - } - ], - "subscriber":{ - "originalPurchaseDate":null, - "originalApplicationVersion":null, - "allPurchaseDatesMillis":{}, - "entitlements":{ - "active":{}, - "all":{} - }, - "allPurchaseDates":{}, - "allExpirationDatesMillis":{}, - "allExpirationDates":{}, - "originalAppUserId":"$RCAnonymousID:z6xyxaasvt841d5zttw7q2iisb023tf", - "latestExpirationDate":null, - "requestDate":"2023-08-03T00:29:53.000Z", - "latestExpirationDateMillis":null, - "nonSubscriptionTransactions":[], - "originalPurchaseDateMillis":null, - "managementURL":null, - "allPurchasedProductIdentifiers":[], - "firstSeen":"2023-08-03T00:29:53.000Z", - "activeSubscriptions":[] - } - })"; - - nlohmann::json request = nlohmann::json::parse(json_str, nullptr, false); - - request["user"] = user_id; - request["subscriber"]["originalAppUserId"] = std::format("$RCAnonymousID:{}", app_id); - request["subscriber"]["firstSeen"] = request_date; - request["subscriber"]["requestDate"] = request_date; - request["messages"][0]["content"] = std::format("user: {}\nassistant:", prompt); - - SPDLOG_INFO("{}", request.dump(2)); - - req.body() = request.dump(); - req.prepare_payload(); - - auto ret = co_await sendRequestRecvResponse(req, host, port, std::bind_front(&FreeGpt::createHttpClient, *this)); - if (!ret.has_value()) { - co_await ch->async_send(err, ret.error(), use_nothrow_awaitable); - co_return; - } - auto& [res, ctx, stream_] = ret.value(); - if (boost::beast::http::status::ok != res.result()) { - SPDLOG_ERROR("http status code: {}", res.result_int()); - co_await ch->async_send(err, res.reason(), use_nothrow_awaitable); - co_return; - } - - nlohmann::json rsp = nlohmann::json::parse(res.body(), nullptr, false); - if (rsp.is_discarded()) { - SPDLOG_ERROR("json parse error"); - co_await ch->async_send(err, std::format("json parse error: {}", res.body()), use_nothrow_awaitable); - co_return; - } - if (!rsp.contains("message")) { - SPDLOG_ERROR("not contains message: {}", rsp.dump()); - co_await ch->async_send(err, std::format("not contains message : {}", rsp.dump()), use_nothrow_awaitable); - co_return; - } - co_await ch->async_send(err, rsp["message"].value("content", rsp.dump()), use_nothrow_awaitable); - co_return; -} - boost::asio::awaitable FreeGpt::acytoo(std::shared_ptr ch, nlohmann::json json) { boost::system::error_code err{}; ScopeExit auto_exit{[&] { ch->close(); }}; @@ -1606,7 +1527,7 @@ boost::asio::awaitable FreeGpt::huggingChat(std::shared_ptr ch, n req_init_conversation.set(boost::beast::http::field::user_agent, user_agent); req_init_conversation.set("Accept", "*/*"); req_init_conversation.set("Content-Type", "application/json"); - req_init_conversation.body() = R"({"model": "OpenAssistant/oasst-sft-6-llama-30b-xor"})"; + req_init_conversation.body() = R"({"model": "meta-llama/Llama-2-70b-chat-hf"})"; req_init_conversation.prepare_payload(); auto [ec, count] = co_await boost::beast::http::async_write(stream_, req_init_conversation, use_nothrow_awaitable); @@ -1902,87 +1823,6 @@ boost::asio::awaitable FreeGpt::binjie(std::shared_ptr ch, nlohma co_return; } -boost::asio::awaitable FreeGpt::codeLinkAva(std::shared_ptr ch, nlohmann::json json) { - boost::system::error_code err{}; - ScopeExit auto_exit{[&] { ch->close(); }}; - - auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); - - constexpr std::string_view host = "ava-alpha-api.codelink.io"; - constexpr std::string_view port = "443"; - - constexpr std::string_view user_agent{ - R"(Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36)"}; - - boost::asio::ssl::context ctx(boost::asio::ssl::context::tls); - ctx.set_verify_mode(boost::asio::ssl::verify_none); - - auto client = co_await createHttpClient(ctx, host, port); - if (!client.has_value()) { - SPDLOG_ERROR("createHttpClient: {}", client.error()); - co_await ch->async_send(err, client.error(), use_nothrow_awaitable); - co_return; - } - auto& stream_ = client.value(); - - boost::beast::http::request req{boost::beast::http::verb::post, "/api/chat", 11}; - req.set(boost::beast::http::field::host, host); - req.set(boost::beast::http::field::user_agent, user_agent); - req.set("Accept", "*/*"); - req.set("accept-language", "en,fr-FR;q=0.9,fr;q=0.8,es-ES;q=0.7,es;q=0.6,en-US;q=0.5,am;q=0.4,de;q=0.3"); - req.set("origin", "https://ava-ai-ef611.web.app"); - req.set("referer", "https://ava-ai-ef611.web.app/"); - req.set(boost::beast::http::field::content_type, "application/json"); - req.set("sec-fetch-dest", "empty"); - req.set("sec-fetch-mode", "cors"); - req.set("sec-fetch-site", "same-origin"); - - constexpr std::string_view json_str = R"({ - "messages": [ - { - "role": "user", - "content": "hello" - } - ], - "stream": true, - "temperature": 0.6 - })"; - nlohmann::json request = nlohmann::json::parse(json_str, nullptr, false); - - request["messages"][0]["content"] = prompt; - SPDLOG_INFO("{}", request.dump(2)); - - req.body() = request.dump(); - req.prepare_payload(); - - std::string recv; - auto result = co_await sendRequestRecvChunk(ch, stream_, req, 200, [&ch, &recv](std::string chunk_str) { - recv.append(chunk_str); - while (true) { - auto position = recv.find("\n"); - if (position == std::string::npos) - break; - auto msg = recv.substr(0, position + 1); - recv.erase(0, position + 1); - msg.pop_back(); - if (msg.empty() || !msg.contains("content")) - continue; - auto fields = splitString(msg, "data:"); - boost::system::error_code err{}; - nlohmann::json line_json = nlohmann::json::parse(fields.back(), nullptr, false); - if (line_json.is_discarded()) { - SPDLOG_ERROR("json parse error: [{}]", fields.back()); - ch->try_send(err, std::format("json parse error: [{}]", fields.back())); - continue; - } - auto str = line_json["choices"][0]["delta"]["content"].get(); - if (!str.empty()) - ch->try_send(err, str); - } - }); - co_return; -} - boost::asio::awaitable FreeGpt::chatBase(std::shared_ptr ch, nlohmann::json json) { boost::system::error_code err{}; ScopeExit auto_exit{[&] { ch->close(); }}; @@ -2703,3 +2543,318 @@ boost::asio::awaitable FreeGpt::chatGptDuo(std::shared_ptr ch, nl } co_return; } + +boost::asio::awaitable FreeGpt::chatForAi(std::shared_ptr ch, nlohmann::json json) { + co_await boost::asio::post(boost::asio::bind_executor(*m_thread_pool_ptr, boost::asio::use_awaitable)); + + boost::system::error_code err{}; + ScopeExit _exit{[=] { boost::asio::post(ch->get_executor(), [=] { ch->close(); }); }}; + auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); + + CURLcode res; + CURL* curl = curl_easy_init(); + if (!curl) { + auto error_info = std::format("curl_easy_init() failed:{}", curl_easy_strerror(res)); + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, error_info); + co_return; + } + curl_easy_setopt(curl, CURLOPT_URL, "https://chatforai.com/api/handle/provider-openai"); + if (!m_cfg.http_proxy.empty()) + curl_easy_setopt(curl, CURLOPT_PROXY, m_cfg.http_proxy.c_str()); + + struct Input { + std::shared_ptr ch; + std::string recv; + }; + Input input{ch}; + auto action_cb = [](void* contents, size_t size, size_t nmemb, void* userp) -> size_t { + boost::system::error_code err{}; + auto input_ptr = static_cast(userp); + std::string data{(char*)contents, size * nmemb}; + auto& [ch, recv] = *input_ptr; + boost::asio::post(ch->get_executor(), [=] { ch->try_send(err, data); }); + return size * nmemb; + }; + size_t (*action_fn)(void* contents, size_t size, size_t nmemb, void* userp) = action_cb; + curlEasySetopt(curl); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, action_fn); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &input); + + auto generate_signature = [](int timestamp, const std::string& conversation_id, const std::string& message) { + std::stringstream ss; + ss << timestamp << ":" << conversation_id << ":" << message << ":6B46K4pt"; + std::string data = ss.str(); + + unsigned char digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast(data.c_str()), data.length(), digest); + + std::stringstream sha_stream; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + sha_stream << std::setfill('0') << std::setw(2) << std::hex << static_cast(digest[i]); + } + return sha_stream.str(); + }; + uint64_t timestamp = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + auto onversation_id = std::format("id_{}", timestamp); + std::string signature = generate_signature(timestamp, onversation_id, prompt); + + constexpr std::string_view request_str{R"({ + "conversationId": "id_1696338587", + "conversationType": "chat_continuous", + "botId": "chat_continuous", + "globalSettings": { + "baseUrl": "https://api.openai.com", + "model": "gpt-3.5-turbo", + "messageHistorySize": 5, + "temperature": 0.7, + "top_p": 1, + "stream": true + }, + "botSettings": {}, + "prompt": "hello", + "messages": [{ + "role": "user", + "content": "hello" + }], + "sign": "1505ec882d72d5f3175a74ac84d665b1b904e6671b2e7334268f540975929a26", + "timestamp": 1696338587 + })"}; + nlohmann::json request = nlohmann::json::parse(request_str, nullptr, false); + + request["sign"] = signature; + request["conversationId"] = onversation_id; + request["messages"] = getConversationJson(json); + request["timestamp"] = timestamp; + + auto str = request.dump(); + SPDLOG_INFO("request : [{}]", str); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, str.c_str()); + + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + ScopeExit auto_exit{[=] { + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + }}; + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + auto error_info = std::format("curl_easy_perform() failed:{}", curl_easy_strerror(res)); + ch->try_send(err, error_info); + co_return; + } + int32_t response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, std::format("you http code:{}", response_code)); + co_return; + } + co_return; +} + +boost::asio::awaitable FreeGpt::freeGpt(std::shared_ptr ch, nlohmann::json json) { + co_await boost::asio::post(boost::asio::bind_executor(*m_thread_pool_ptr, boost::asio::use_awaitable)); + + boost::system::error_code err{}; + ScopeExit _exit{[=] { boost::asio::post(ch->get_executor(), [=] { ch->close(); }); }}; + auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); + + CURLcode res; + CURL* curl = curl_easy_init(); + if (!curl) { + auto error_info = std::format("curl_easy_init() failed:{}", curl_easy_strerror(res)); + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, error_info); + co_return; + } + curl_easy_setopt(curl, CURLOPT_URL, "https://k.aifree.site/api/generate"); + if (!m_cfg.http_proxy.empty()) + curl_easy_setopt(curl, CURLOPT_PROXY, m_cfg.http_proxy.c_str()); + + struct Input { + std::shared_ptr ch; + std::string recv; + }; + Input input{ch}; + auto action_cb = [](void* contents, size_t size, size_t nmemb, void* userp) -> size_t { + boost::system::error_code err{}; + auto input_ptr = static_cast(userp); + std::string data{(char*)contents, size * nmemb}; + auto& [ch, recv] = *input_ptr; + boost::asio::post(ch->get_executor(), [=] { ch->try_send(err, data); }); + return size * nmemb; + }; + size_t (*action_fn)(void* contents, size_t size, size_t nmemb, void* userp) = action_cb; + curlEasySetopt(curl); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, action_fn); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &input); + + auto generate_signature = [](int timestamp, const std::string& message, const std::string& secret = "") { + std::stringstream ss; + ss << timestamp << ":" << message << ":" << secret; + std::string data = ss.str(); + + unsigned char digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast(data.c_str()), data.length(), digest); + + std::stringstream sha_stream; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + sha_stream << std::setfill('0') << std::setw(2) << std::hex << static_cast(digest[i]); + } + return sha_stream.str(); + }; + uint64_t timestamp = + std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + std::string signature = generate_signature(timestamp, prompt); + + constexpr std::string_view request_str{R"({ + "messages":[ + { + "role":"user", + "content":"hello" + } + ], + "pass":null, + "sign":"7c2700b5813053ff8000cb9fb1ebdadbfcf62882829da59e4474bee466de7c89", + "time":1695716667 + })"}; + nlohmann::json request = nlohmann::json::parse(request_str, nullptr, false); + + request["sign"] = signature; + request["time"] = timestamp; + request["messages"] = getConversationJson(json); + + auto str = request.dump(); + SPDLOG_INFO("request : [{}]", str); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, str.c_str()); + + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + ScopeExit auto_exit{[=] { + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + }}; + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + auto error_info = std::format("curl_easy_perform() failed:{}", curl_easy_strerror(res)); + ch->try_send(err, error_info); + co_return; + } + int32_t response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, std::format("you http code:{}", response_code)); + co_return; + } + co_return; +} + +boost::asio::awaitable FreeGpt::cromicle(std::shared_ptr ch, nlohmann::json json) { + co_await boost::asio::post(boost::asio::bind_executor(*m_thread_pool_ptr, boost::asio::use_awaitable)); + + boost::system::error_code err{}; + ScopeExit _exit{[=] { boost::asio::post(ch->get_executor(), [=] { ch->close(); }); }}; + auto prompt = json.at("meta").at("content").at("parts").at(0).at("content").get(); + + CURLcode res; + CURL* curl = curl_easy_init(); + if (!curl) { + auto error_info = std::format("curl_easy_init() failed:{}", curl_easy_strerror(res)); + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, error_info); + co_return; + } + curl_easy_setopt(curl, CURLOPT_URL, "https://cromicle.top/chat"); + if (!m_cfg.http_proxy.empty()) + curl_easy_setopt(curl, CURLOPT_PROXY, m_cfg.http_proxy.c_str()); + + struct Input { + std::shared_ptr ch; + std::string recv; + }; + Input input{ch}; + auto action_cb = [](void* contents, size_t size, size_t nmemb, void* userp) -> size_t { + boost::system::error_code err{}; + auto input_ptr = static_cast(userp); + std::string data{(char*)contents, size * nmemb}; + auto& [ch, recv] = *input_ptr; + boost::asio::post(ch->get_executor(), [=] { ch->try_send(err, data); }); + return size * nmemb; + }; + size_t (*action_fn)(void* contents, size_t size, size_t nmemb, void* userp) = action_cb; + curlEasySetopt(curl); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, action_fn); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &input); + + auto generate_signature = [](const std::string& message) { + std::stringstream ss; + ss << "asdap" << message; + std::string data = ss.str(); + + unsigned char digest[SHA256_DIGEST_LENGTH]; + SHA256(reinterpret_cast(data.c_str()), data.length(), digest); + + std::stringstream sha_stream; + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + sha_stream << std::setfill('0') << std::setw(2) << std::hex << static_cast(digest[i]); + } + return sha_stream.str(); + }; + std::string signature = generate_signature(prompt); + + constexpr std::string_view request_str{R"({ + "message": "hello", + "hash": "dda6ea4e1dc215f198084018b1df20cfeafe9fbdfe31d8a350d6917509158d8a", + "token": "asdap" + })"}; + nlohmann::json request = nlohmann::json::parse(request_str, nullptr, false); + + request["hash"] = signature; + request["message"] = prompt; + + auto str = request.dump(); + SPDLOG_INFO("request : [{}]", str); + + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, str.c_str()); + + struct curl_slist* headers = nullptr; + headers = curl_slist_append(headers, "Content-Type: application/json"); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + + ScopeExit auto_exit{[=] { + curl_slist_free_all(headers); + curl_easy_cleanup(curl); + }}; + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + auto error_info = std::format("curl_easy_perform() failed:{}", curl_easy_strerror(res)); + ch->try_send(err, error_info); + co_return; + } + int32_t response_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code); + if (response_code != 200) { + co_await boost::asio::post(boost::asio::bind_executor(ch->get_executor(), boost::asio::use_awaitable)); + ch->try_send(err, std::format("you http code:{}", response_code)); + co_return; + } + co_return; +} diff --git a/chat_gpt_microservice/src/main.cpp b/chat_gpt_microservice/src/main.cpp index 8072c10..ee959ad 100644 --- a/chat_gpt_microservice/src/main.cpp +++ b/chat_gpt_microservice/src/main.cpp @@ -92,6 +92,18 @@ boost::asio::awaitable sendHttpResponse(auto& stream, auto& request, auto co_return; } +void setContentType(auto& res, const std::string& file) { + SPDLOG_INFO("file: {}", file); + if (file.ends_with("js")) { + res.set(boost::beast::http::field::content_type, "text/javascript"); + } else if (file.ends_with("css")) { + res.set(boost::beast::http::field::content_type, "text/css"); + } else if (file.ends_with("png")) { + res.set(boost::beast::http::field::content_type, "image/png"); + } else + SPDLOG_ERROR("invalid file type: {}", file); +} + boost::asio::awaitable startSession(boost::asio::ip::tcp::socket sock, Config& cfg, boost::asio::io_context& context) { boost::beast::tcp_stream stream{std::move(sock)}; @@ -172,7 +184,7 @@ boost::asio::awaitable startSession(boost::asio::ip::tcp::socket sock, Con boost::beast::http::response res{boost::beast::http::status::ok, request.version()}; res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, "text/javascript"); + setContentType(res, file); res.keep_alive(request.keep_alive()); res.body() = std::move(chat_js_content); res.prepare_payload(); @@ -191,8 +203,7 @@ boost::asio::awaitable startSession(boost::asio::ip::tcp::socket sock, Con std::piecewise_construct, std::make_tuple(std::move(body)), std::make_tuple(boost::beast::http::status::ok, request.version())}; res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, - req_path.contains("css") ? "text/css" : "text/javascript"); + setContentType(res, file); res.content_length(size); res.keep_alive(request.keep_alive()); boost::beast::http::message_generator rsp = std::move(res); @@ -324,24 +335,25 @@ int main(int argc, char** argv) { ADD_METHOD("gpt-3.5-turbo-stream-openai", FreeGpt::openAi); ADD_METHOD("gpt-3.5-turbo-Aichat", FreeGpt::aiChat); ADD_METHOD("gpt-4-ChatgptAi", FreeGpt::chatGptAi); - // ADD_METHOD("gpt-3.5-turbo-weWordle", FreeGpt::weWordle); ADD_METHOD("gpt-3.5-turbo-acytoo", FreeGpt::acytoo); ADD_METHOD("gpt-3.5-turbo-stream-DeepAi", FreeGpt::deepAi); ADD_METHOD("gpt-3.5-turbo-stream-H2o", FreeGpt::h2o); ADD_METHOD("gpt-3.5-turbo-stream-yqcloud", FreeGpt::yqcloud); ADD_METHOD("gpt-OpenAssistant-stream-HuggingChat", FreeGpt::huggingChat) ADD_METHOD("gpt-4-turbo-stream-you", FreeGpt::you); - ADD_METHOD("gpt-3.5-turbo-AItianhu", FreeGpt::aiTianhu); + // ADD_METHOD("gpt-3.5-turbo-AItianhu", FreeGpt::aiTianhu); ADD_METHOD("gpt-3-stream-binjie", FreeGpt::binjie); - // ADD_METHOD("gpt-3.5-turbo-stream-CodeLinkAva", FreeGpt::codeLinkAva); ADD_METHOD("gpt-4-stream-ChatBase", FreeGpt::chatBase); ADD_METHOD("gpt-3.5-turbo-stream-aivvm", FreeGpt::aivvm); ADD_METHOD("gpt-3.5-turbo-16k-stream-Ylokh", FreeGpt::ylokh); ADD_METHOD("gpt-3.5-turbo-stream-Vitalentum", FreeGpt::vitalentum); ADD_METHOD("gpt-3.5-turbo-stream-GptGo", FreeGpt::gptGo); - ADD_METHOD("gpt-3.5-turbo-stream-AItianhuSpace", FreeGpt::aiTianhuSpace); + // ADD_METHOD("gpt-3.5-turbo-stream-AItianhuSpace", FreeGpt::aiTianhuSpace); ADD_METHOD("gpt-3.5-turbo-stream-Aibn", FreeGpt::aibn); ADD_METHOD("gpt-3.5-turbo-ChatgptDuo", FreeGpt::chatGptDuo); + ADD_METHOD("gpt-3.5-turbo-stream-ChatForAi", FreeGpt::chatForAi); + ADD_METHOD("gpt-3.5-turbo-stream-FreeGpt", FreeGpt::freeGpt); + ADD_METHOD("gpt-3.5-turbo-stream-Cromicle", FreeGpt::cromicle); IoContextPool pool{cfg.work_thread_num}; pool.start(); diff --git a/poetry.lock b/poetry.lock index e553189..ca34199 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,14 +12,48 @@ files = [ ] [[package]] -name = "annotated-types" -version = "0.5.0" -description = "Reusable constraint types to use with typing.Annotated" +name = "aiosqlite" +version = "0.19.0" +description = "asyncio bridge to the standard sqlite3 module" optional = false python-versions = ">=3.7" files = [ - {file = "annotated_types-0.5.0-py3-none-any.whl", hash = "sha256:58da39888f92c276ad970249761ebea80ba544b77acddaa1a4d6cf78287d45fd"}, - {file = "annotated_types-0.5.0.tar.gz", hash = "sha256:47cdc3490d9ac1506ce92c7aaa76c579dc3509ff11e098fc867e5130ab7be802"}, + {file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"}, + {file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"}, +] + +[package.extras] +dev = ["aiounittest (==1.4.1)", "attribution (==1.6.2)", "black (==23.3.0)", "coverage[toml] (==7.2.3)", "flake8 (==5.0.4)", "flake8-bugbear (==23.3.12)", "flit (==3.7.1)", "mypy (==1.2.0)", "ufmt (==2.1.0)", "usort (==1.0.6)"] +docs = ["sphinx (==6.1.3)", "sphinx-mdinclude (==0.5.3)"] + +[[package]] +name = "alembic" +version = "1.12.0" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.7" +files = [ + {file = "alembic-1.12.0-py3-none-any.whl", hash = "sha256:03226222f1cf943deee6c85d9464261a6c710cd19b4fe867a3ad1f25afda610f"}, + {file = "alembic-1.12.0.tar.gz", hash = "sha256:8e7645c32e4f200675e69f0745415335eb59a3663f5feb487abfa0b30c45888b"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["python-dateutil"] + +[[package]] +name = "annotated-types" +version = "0.6.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +files = [ + {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, + {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, ] [[package]] @@ -1031,75 +1065,77 @@ docs = ["sphinx (>=2.1.2,<3.0.0)", "sphinx-autodoc-typehints (>=1.6.0,<2.0.0)", [[package]] name = "greenlet" -version = "2.0.2" +version = "3.0.0" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +python-versions = ">=3.7" files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, + {file = "greenlet-3.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e09dea87cc91aea5500262993cbd484b41edf8af74f976719dd83fe724644cd6"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47932c434a3c8d3c86d865443fadc1fbf574e9b11d6650b656e602b1797908a"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bdfaeecf8cc705d35d8e6de324bf58427d7eafb55f67050d8f28053a3d57118c"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a68d670c8f89ff65c82b936275369e532772eebc027c3be68c6b87ad05ca695"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38ad562a104cd41e9d4644f46ea37167b93190c6d5e4048fcc4b80d34ecb278f"}, + {file = "greenlet-3.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02a807b2a58d5cdebb07050efe3d7deaf915468d112dfcf5e426d0564aa3aa4a"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b1660a15a446206c8545edc292ab5c48b91ff732f91b3d3b30d9a915d5ec4779"}, + {file = "greenlet-3.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:813720bd57e193391dfe26f4871186cf460848b83df7e23e6bef698a7624b4c9"}, + {file = "greenlet-3.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:aa15a2ec737cb609ed48902b45c5e4ff6044feb5dcdfcf6fa8482379190330d7"}, + {file = "greenlet-3.0.0-cp310-universal2-macosx_11_0_x86_64.whl", hash = "sha256:7709fd7bb02b31908dc8fd35bfd0a29fc24681d5cc9ac1d64ad07f8d2b7db62f"}, + {file = "greenlet-3.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:211ef8d174601b80e01436f4e6905aca341b15a566f35a10dd8d1e93f5dbb3b7"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6512592cc49b2c6d9b19fbaa0312124cd4c4c8a90d28473f86f92685cc5fef8e"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:871b0a8835f9e9d461b7fdaa1b57e3492dd45398e87324c047469ce2fc9f516c"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b505fcfc26f4148551826a96f7317e02c400665fa0883fe505d4fcaab1dabfdd"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123910c58234a8d40eaab595bc56a5ae49bdd90122dde5bdc012c20595a94c14"}, + {file = "greenlet-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:96d9ea57292f636ec851a9bb961a5cc0f9976900e16e5d5647f19aa36ba6366b"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b72b802496cccbd9b31acea72b6f87e7771ccfd7f7927437d592e5c92ed703c"}, + {file = "greenlet-3.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:527cd90ba3d8d7ae7dceb06fda619895768a46a1b4e423bdb24c1969823b8362"}, + {file = "greenlet-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:37f60b3a42d8b5499be910d1267b24355c495064f271cfe74bf28b17b099133c"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1482fba7fbed96ea7842b5a7fc11d61727e8be75a077e603e8ab49d24e234383"}, + {file = "greenlet-3.0.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:be557119bf467d37a8099d91fbf11b2de5eb1fd5fc5b91598407574848dc910f"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:73b2f1922a39d5d59cc0e597987300df3396b148a9bd10b76a058a2f2772fc04"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1e22c22f7826096ad503e9bb681b05b8c1f5a8138469b255eb91f26a76634f2"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d363666acc21d2c204dd8705c0e0457d7b2ee7a76cb16ffc099d6799744ac99"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:334ef6ed8337bd0b58bb0ae4f7f2dcc84c9f116e474bb4ec250a8bb9bd797a66"}, + {file = "greenlet-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6672fdde0fd1a60b44fb1751a7779c6db487e42b0cc65e7caa6aa686874e79fb"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:952256c2bc5b4ee8df8dfc54fc4de330970bf5d79253c863fb5e6761f00dda35"}, + {file = "greenlet-3.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:269d06fa0f9624455ce08ae0179430eea61085e3cf6457f05982b37fd2cefe17"}, + {file = "greenlet-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9adbd8ecf097e34ada8efde9b6fec4dd2a903b1e98037adf72d12993a1c80b51"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6b5ce7f40f0e2f8b88c28e6691ca6806814157ff05e794cdd161be928550f4c"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf94aa539e97a8411b5ea52fc6ccd8371be9550c4041011a091eb8b3ca1d810"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80dcd3c938cbcac986c5c92779db8e8ce51a89a849c135172c88ecbdc8c056b7"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e52a712c38e5fb4fd68e00dc3caf00b60cb65634d50e32281a9d6431b33b4af1"}, + {file = "greenlet-3.0.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5539f6da3418c3dc002739cb2bb8d169056aa66e0c83f6bacae0cd3ac26b423"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:343675e0da2f3c69d3fb1e894ba0a1acf58f481f3b9372ce1eb465ef93cf6fed"}, + {file = "greenlet-3.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:abe1ef3d780de56defd0c77c5ba95e152f4e4c4e12d7e11dd8447d338b85a625"}, + {file = "greenlet-3.0.0-cp37-cp37m-win32.whl", hash = "sha256:e693e759e172fa1c2c90d35dea4acbdd1d609b6936115d3739148d5e4cd11947"}, + {file = "greenlet-3.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bdd696947cd695924aecb3870660b7545a19851f93b9d327ef8236bfc49be705"}, + {file = "greenlet-3.0.0-cp37-universal2-macosx_11_0_x86_64.whl", hash = "sha256:cc3e2679ea13b4de79bdc44b25a0c4fcd5e94e21b8f290791744ac42d34a0353"}, + {file = "greenlet-3.0.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:63acdc34c9cde42a6534518e32ce55c30f932b473c62c235a466469a710bfbf9"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a1a6244ff96343e9994e37e5b4839f09a0207d35ef6134dce5c20d260d0302c"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b822fab253ac0f330ee807e7485769e3ac85d5eef827ca224feaaefa462dc0d0"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8060b32d8586e912a7b7dac2d15b28dbbd63a174ab32f5bc6d107a1c4143f40b"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:621fcb346141ae08cb95424ebfc5b014361621b8132c48e538e34c3c93ac7365"}, + {file = "greenlet-3.0.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb36985f606a7c49916eff74ab99399cdfd09241c375d5a820bb855dfb4af9f"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10b5582744abd9858947d163843d323d0b67be9432db50f8bf83031032bc218d"}, + {file = "greenlet-3.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f351479a6914fd81a55c8e68963609f792d9b067fb8a60a042c585a621e0de4f"}, + {file = "greenlet-3.0.0-cp38-cp38-win32.whl", hash = "sha256:9de687479faec7db5b198cc365bc34addd256b0028956501f4d4d5e9ca2e240a"}, + {file = "greenlet-3.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:3fd2b18432e7298fcbec3d39e1a0aa91ae9ea1c93356ec089421fabc3651572b"}, + {file = "greenlet-3.0.0-cp38-universal2-macosx_11_0_x86_64.whl", hash = "sha256:3c0d36f5adc6e6100aedbc976d7428a9f7194ea79911aa4bf471f44ee13a9464"}, + {file = "greenlet-3.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4cd83fb8d8e17633ad534d9ac93719ef8937568d730ef07ac3a98cb520fd93e4"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a5b2d4cdaf1c71057ff823a19d850ed5c6c2d3686cb71f73ae4d6382aaa7a06"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e7dcdfad252f2ca83c685b0fa9fba00e4d8f243b73839229d56ee3d9d219314"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94e4e924d09b5a3e37b853fe5924a95eac058cb6f6fb437ebb588b7eda79870"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad6fb737e46b8bd63156b8f59ba6cdef46fe2b7db0c5804388a2d0519b8ddb99"}, + {file = "greenlet-3.0.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d55db1db455c59b46f794346efce896e754b8942817f46a1bada2d29446e305a"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:56867a3b3cf26dc8a0beecdb4459c59f4c47cdd5424618c08515f682e1d46692"}, + {file = "greenlet-3.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9a812224a5fb17a538207e8cf8e86f517df2080c8ee0f8c1ed2bdaccd18f38f4"}, + {file = "greenlet-3.0.0-cp39-cp39-win32.whl", hash = "sha256:0d3f83ffb18dc57243e0151331e3c383b05e5b6c5029ac29f754745c800f8ed9"}, + {file = "greenlet-3.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:831d6f35037cf18ca5e80a737a27d822d87cd922521d18ed3dbc8a6967be50ce"}, + {file = "greenlet-3.0.0-cp39-universal2-macosx_11_0_x86_64.whl", hash = "sha256:a048293392d4e058298710a54dfaefcefdf49d287cd33fb1f7d63d55426e4355"}, + {file = "greenlet-3.0.0.tar.gz", hash = "sha256:19834e3f91f485442adc1ee440171ec5d9a4840a1f7bd5ed97833544719ce10b"}, ] [package.extras] -docs = ["Sphinx", "docutils (<0.18)"] +docs = ["Sphinx"] test = ["objgraph", "psutil"] [[package]] @@ -1156,13 +1192,13 @@ lxml = ["lxml"] [[package]] name = "httpcore" -version = "0.17.3" +version = "0.18.0" description = "A minimal low-level HTTP client." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpcore-0.17.3-py3-none-any.whl", hash = "sha256:c2789b767ddddfa2a5782e3199b2b7f6894540b17b16ec26b2c4d8e103510b87"}, - {file = "httpcore-0.17.3.tar.gz", hash = "sha256:a6f30213335e34c1ade7be6ec7c47f19f50c56db36abef1a9dfa3815b1cb3888"}, + {file = "httpcore-0.18.0-py3-none-any.whl", hash = "sha256:adc5398ee0a476567bf87467063ee63584a8bce86078bf748e48754f60202ced"}, + {file = "httpcore-0.18.0.tar.gz", hash = "sha256:13b5e5cd1dca1a6636a6aaea212b19f4f85cd88c366a2b82304181b769aab3c9"}, ] [package.dependencies] @@ -1177,18 +1213,18 @@ socks = ["socksio (==1.*)"] [[package]] name = "httpx" -version = "0.24.1" +version = "0.25.0" description = "The next generation HTTP client." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "httpx-0.24.1-py3-none-any.whl", hash = "sha256:06781eb9ac53cde990577af654bd990a4949de37a28bdb4a230d434f3a30b9bd"}, - {file = "httpx-0.24.1.tar.gz", hash = "sha256:5853a43053df830c20f8110c5e69fe44d035d850b2dfe795e196f00fdb774bdd"}, + {file = "httpx-0.25.0-py3-none-any.whl", hash = "sha256:181ea7f8ba3a82578be86ef4171554dd45fec26a02556a744db029a0a27b7100"}, + {file = "httpx-0.25.0.tar.gz", hash = "sha256:47ecda285389cb32bb2691cc6e069e3ab0205956f681c5b2ad2325719751d875"}, ] [package.dependencies] certifi = "*" -httpcore = ">=0.15.0,<0.18.0" +httpcore = ">=0.18.0,<0.19.0" idna = "*" sniffio = "*" @@ -1347,6 +1383,25 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "exceptiongroup (==1.1.3)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "mypy (==v1.5.1)", "pre-commit (==3.4.0)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] +[[package]] +name = "mako" +version = "1.2.4" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Mako-1.2.4-py3-none-any.whl", hash = "sha256:c97c79c018b9165ac9922ae4f32da095ffd3c4e6872b45eded42926deea46818"}, + {file = "Mako-1.2.4.tar.gz", hash = "sha256:d60a3903dc3bb01a18ad6a89cdbe2e4eadc69c0bc8ef1e3773ba53d44c3f7a34"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -1551,6 +1606,89 @@ files = [ {file = "msgpack-1.0.7.tar.gz", hash = "sha256:572efc93db7a4d27e404501975ca6d2d9775705c2d922390d878fcf768d92c87"}, ] +[[package]] +name = "multidict" +version = "6.0.4" +description = "multidict implementation" +optional = false +python-versions = ">=3.7" +files = [ + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, + {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, + {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, + {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, + {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, + {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, + {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, + {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, + {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, + {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, + {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, + {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, + {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, + {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, + {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, + {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, + {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, + {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, + {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, + {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, + {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, + {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, + {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, + {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, + {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, + {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, + {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, +] + [[package]] name = "mypy" version = "1.5.1" @@ -2474,33 +2612,33 @@ cli = ["click (>=5.0)"] [[package]] name = "python-telegram-bot" -version = "20.5" +version = "20.6" description = "We have made you a wrapper you can't refuse" optional = false python-versions = ">=3.8" files = [ - {file = "python-telegram-bot-20.5.tar.gz", hash = "sha256:2f45a94c861cbd40440ece2be176ef0fc69e10d84e6dfa17f9a456e32aeece13"}, - {file = "python_telegram_bot-20.5-py3-none-any.whl", hash = "sha256:fc9605a855794231c802cc3948e6f7c319a817b5cd1827371f170bc7ca0ca279"}, + {file = "python-telegram-bot-20.6.tar.gz", hash = "sha256:c6951dc6b9368d80a17b5d0496ced39bca2c9bafab06cbfdae6226493d8b06fe"}, + {file = "python_telegram_bot-20.6-py3-none-any.whl", hash = "sha256:be8ebda350fd0c69c3ebdd7ce87b1e6f8b3fb1d218a193dd821d1d6285b9f649"}, ] [package.dependencies] aiolimiter = {version = ">=1.1.0,<1.2.0", optional = true, markers = "extra == \"ext\""} APScheduler = {version = ">=3.10.4,<3.11.0", optional = true, markers = "extra == \"ext\""} cachetools = {version = ">=5.3.1,<5.4.0", optional = true, markers = "extra == \"ext\""} -httpx = ">=0.24.1,<0.25.0" +httpx = ">=0.25.0,<0.26.0" pytz = {version = ">=2018.6", optional = true, markers = "extra == \"ext\""} -tornado = {version = ">=6.2,<7.0", optional = true, markers = "extra == \"ext\""} +tornado = {version = ">=6.3.3,<6.4.0", optional = true, markers = "extra == \"ext\""} [package.extras] -all = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.1,<5.4.0)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.2,<7.0)"] +all = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.1,<5.4.0)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.3.3,<6.4.0)"] callback-data = ["cachetools (>=5.3.1,<5.4.0)"] -ext = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.1,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.2,<7.0)"] +ext = ["APScheduler (>=3.10.4,<3.11.0)", "aiolimiter (>=1.1.0,<1.2.0)", "cachetools (>=5.3.1,<5.4.0)", "pytz (>=2018.6)", "tornado (>=6.3.3,<6.4.0)"] http2 = ["httpx[http2]"] job-queue = ["APScheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"] passport = ["cryptography (>=39.0.1)"] rate-limiter = ["aiolimiter (>=1.1.0,<1.2.0)"] socks = ["httpx[socks]"] -webhooks = ["tornado (>=6.2,<7.0)"] +webhooks = ["tornado (>=6.3.3,<6.4.0)"] [[package]] name = "pytz" @@ -2631,17 +2769,17 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruamel-yaml" -version = "0.17.33" +version = "0.17.35" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3" files = [ - {file = "ruamel.yaml-0.17.33-py3-none-any.whl", hash = "sha256:2080c7a02b8a30fb3c06727cdf3e254a64055eedf3aa2d17c2b669639c04971b"}, - {file = "ruamel.yaml-0.17.33.tar.gz", hash = "sha256:5c56aa0bff2afceaa93bffbfc78b450b7dc1e01d5edb80b3a570695286ae62b1"}, + {file = "ruamel.yaml-0.17.35-py3-none-any.whl", hash = "sha256:b105e3e6fc15b41fdb201ba1b95162ae566a4ef792b9f884c46b4ccc5513a87a"}, + {file = "ruamel.yaml-0.17.35.tar.gz", hash = "sha256:801046a9caacb1b43acc118969b49b96b65e8847f29029563b29ac61d02db61b"}, ] [package.dependencies] -"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.12\""} +"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\""} [package.extras] docs = ["ryd"] @@ -2834,6 +2972,85 @@ requests = ">=2.26.0" [package.extras] whisper-api = ["openai"] +[[package]] +name = "sqlalchemy" +version = "2.0.21" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e7dc99b23e33c71d720c4ae37ebb095bebebbd31a24b7d99dfc4753d2803ede"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7f0c4ee579acfe6c994637527c386d1c22eb60bc1c1d36d940d8477e482095d4"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f7d57a7e140efe69ce2d7b057c3f9a595f98d0bbdfc23fd055efdfbaa46e3a5"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ca38746eac23dd7c20bec9278d2058c7ad662b2f1576e4c3dbfcd7c00cc48fa"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3cf229704074bce31f7f47d12883afee3b0a02bb233a0ba45ddbfe542939cca4"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fb87f763b5d04a82ae84ccff25554ffd903baafba6698e18ebaf32561f2fe4aa"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-win32.whl", hash = "sha256:89e274604abb1a7fd5c14867a412c9d49c08ccf6ce3e1e04fffc068b5b6499d4"}, + {file = "SQLAlchemy-2.0.21-cp310-cp310-win_amd64.whl", hash = "sha256:e36339a68126ffb708dc6d1948161cea2a9e85d7d7b0c54f6999853d70d44430"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf8eebccc66829010f06fbd2b80095d7872991bfe8415098b9fe47deaaa58063"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b977bfce15afa53d9cf6a632482d7968477625f030d86a109f7bdfe8ce3c064a"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ff3dc2f60dbf82c9e599c2915db1526d65415be323464f84de8db3e361ba5b9"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44ac5c89b6896f4740e7091f4a0ff2e62881da80c239dd9408f84f75a293dae9"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:87bf91ebf15258c4701d71dcdd9c4ba39521fb6a37379ea68088ce8cd869b446"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b69f1f754d92eb1cc6b50938359dead36b96a1dcf11a8670bff65fd9b21a4b09"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-win32.whl", hash = "sha256:af520a730d523eab77d754f5cf44cc7dd7ad2d54907adeb3233177eeb22f271b"}, + {file = "SQLAlchemy-2.0.21-cp311-cp311-win_amd64.whl", hash = "sha256:141675dae56522126986fa4ca713739d00ed3a6f08f3c2eb92c39c6dfec463ce"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7614f1eab4336df7dd6bee05bc974f2b02c38d3d0c78060c5faa4cd1ca2af3b8"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d59cb9e20d79686aa473e0302e4a82882d7118744d30bb1dfb62d3c47141b3ec"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a95aa0672e3065d43c8aa80080cdd5cc40fe92dc873749e6c1cf23914c4b83af"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8c323813963b2503e54d0944813cd479c10c636e3ee223bcbd7bd478bf53c178"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:419b1276b55925b5ac9b4c7044e999f1787c69761a3c9756dec6e5c225ceca01"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-win32.whl", hash = "sha256:4615623a490e46be85fbaa6335f35cf80e61df0783240afe7d4f544778c315a9"}, + {file = "SQLAlchemy-2.0.21-cp37-cp37m-win_amd64.whl", hash = "sha256:cca720d05389ab1a5877ff05af96551e58ba65e8dc65582d849ac83ddde3e231"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b4eae01faee9f2b17f08885e3f047153ae0416648f8e8c8bd9bc677c5ce64be9"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3eb7c03fe1cd3255811cd4e74db1ab8dca22074d50cd8937edf4ef62d758cdf4"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2d494b6a2a2d05fb99f01b84cc9af9f5f93bf3e1e5dbdafe4bed0c2823584c1"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b19ae41ef26c01a987e49e37c77b9ad060c59f94d3b3efdfdbf4f3daaca7b5fe"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:fc6b15465fabccc94bf7e38777d665b6a4f95efd1725049d6184b3a39fd54880"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:014794b60d2021cc8ae0f91d4d0331fe92691ae5467a00841f7130fe877b678e"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-win32.whl", hash = "sha256:0268256a34806e5d1c8f7ee93277d7ea8cc8ae391f487213139018b6805aeaf6"}, + {file = "SQLAlchemy-2.0.21-cp38-cp38-win_amd64.whl", hash = "sha256:73c079e21d10ff2be54a4699f55865d4b275fd6c8bd5d90c5b1ef78ae0197301"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:785e2f2c1cb50d0a44e2cdeea5fd36b5bf2d79c481c10f3a88a8be4cfa2c4615"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c111cd40910ffcb615b33605fc8f8e22146aeb7933d06569ac90f219818345ef"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9cba4e7369de663611ce7460a34be48e999e0bbb1feb9130070f0685e9a6b66"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a69067af86ec7f11a8e50ba85544657b1477aabf64fa447fd3736b5a0a4f67"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ccb99c3138c9bde118b51a289d90096a3791658da9aea1754667302ed6564f6e"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:513fd5b6513d37e985eb5b7ed89da5fd9e72354e3523980ef00d439bc549c9e9"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-win32.whl", hash = "sha256:f9fefd6298433b6e9188252f3bff53b9ff0443c8fde27298b8a2b19f6617eeb9"}, + {file = "SQLAlchemy-2.0.21-cp39-cp39-win_amd64.whl", hash = "sha256:2e617727fe4091cedb3e4409b39368f424934c7faa78171749f704b49b4bb4ce"}, + {file = "SQLAlchemy-2.0.21-py3-none-any.whl", hash = "sha256:ea7da25ee458d8f404b93eb073116156fd7d8c2a776d8311534851f28277b4ce"}, + {file = "SQLAlchemy-2.0.21.tar.gz", hash = "sha256:05b971ab1ac2994a14c56b35eaaa91f86ba080e9ad481b20d99d77f381bb6258"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} +mypy = {version = ">=0.910", optional = true, markers = "extra == \"mypy\""} +typing-extensions = ">=4.2.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx-oracle (>=7)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3-binary"] + [[package]] name = "stack-data" version = "0.6.3" @@ -2953,13 +3170,13 @@ files = [ [[package]] name = "traitlets" -version = "5.11.1" +version = "5.11.2" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ - {file = "traitlets-5.11.1-py3-none-any.whl", hash = "sha256:2351372ff87fc912c483d1cb6aa466573d5f44eb4ed9e602c8d0ac012c9daece"}, - {file = "traitlets-5.11.1.tar.gz", hash = "sha256:813584bb569ac4a098c64ca494e8a3bbfef42e37c36a6132bca554aabd111480"}, + {file = "traitlets-5.11.2-py3-none-any.whl", hash = "sha256:98277f247f18b2c5cabaf4af369187754f4fb0e85911d473f72329db8a7f4fae"}, + {file = "traitlets-5.11.2.tar.gz", hash = "sha256:7564b5bf8d38c40fa45498072bf4dc5e8346eb087bbf1e2ae2d8774f6a0f078e"}, ] [package.extras] @@ -3012,13 +3229,13 @@ files = [ [[package]] name = "tzlocal" -version = "5.0.1" +version = "5.1" description = "tzinfo object for the local timezone" optional = false python-versions = ">=3.7" files = [ - {file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"}, - {file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"}, + {file = "tzlocal-5.1-py3-none-any.whl", hash = "sha256:2938498395d5f6a898ab8009555cb37a4d360913ad375d4747ef16826b03ef23"}, + {file = "tzlocal-5.1.tar.gz", hash = "sha256:a5ccb2365b295ed964e0a98ad076fe10c495591e75505d34f154d60a7f1ed722"}, ] [package.dependencies] @@ -3130,7 +3347,94 @@ pyyaml = "*" [package.extras] dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"] +[[package]] +name = "yarl" +version = "1.9.2" +description = "Yet another URL library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"}, + {file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"}, + {file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"}, + {file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"}, + {file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"}, + {file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"}, + {file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"}, + {file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"}, + {file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"}, + {file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"}, + {file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"}, + {file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"}, + {file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"}, + {file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"}, + {file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"}, + {file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"}, + {file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"}, + {file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"}, + {file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"}, + {file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"}, + {file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"}, + {file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"}, + {file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"}, + {file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"}, + {file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"}, + {file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"}, + {file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "ab644b9882ee200392911afc6b71bf87fdb413e4fdd9f06a460ce33da98687d7" +content-hash = "15f814322c6f006b5345d8579bb42198bb7c4953d595ea9662f9e7cae78a979f" diff --git a/pyproject.toml b/pyproject.toml index eb5b148..fbb1201 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,9 +8,9 @@ authors = ["Dmitry Afanasyev "] python = "^3.11" fastapi = "^0.103.1" -python-telegram-bot = {version = "^20.5", extras=["ext"]} +python-telegram-bot = {version = "^20.6", extras=["ext"]} python-dotenv = "^1.0" -httpx = "^0.24" +httpx = "^0.25" loguru = "^0.7" pydantic = "^2.4" pydantic-settings = "^2.0.3" @@ -21,8 +21,12 @@ orjson = "^3.9" sentry-sdk = "^1.31.0" SpeechRecognition = "^3.8" pydub = "^0.25" -greenlet = "^2.0.2" +greenlet = "^3.0" graypy = "^2.1.0" +aiosqlite = "^0.19.0" +yarl = "^1.9.2" +sqlalchemy = {version = "^2.0.21", extras=["mypy"]} +alembic = "^1.12.0" [tool.poetry.dev-dependencies] @@ -159,7 +163,11 @@ warn_unused_configs = true warn_unreachable = true warn_no_return = true exclude = [ - "chat_gpt_microservice" + "chat_gpt_microservice", + "bot_microservice/infra/database/migrations/versions/*" +] +plugins = [ + "sqlalchemy.ext.mypy.plugin", ] [tool.black] diff --git a/scripts/start-bot.sh b/scripts/start-bot.sh index 90e5cb3..c68bf5e 100644 --- a/scripts/start-bot.sh +++ b/scripts/start-bot.sh @@ -1,5 +1,9 @@ #! /bin/bash +set -e + +alembic upgrade "head" + echo "starting the bot" if [[ "${START_WITH_WEBHOOK}" == "true" ]]