From 7ef8d6e19d5dfdf70c8d5a5c0afc8d77cc12cc3f Mon Sep 17 00:00:00 2001 From: Dmitry Afanasyev <71835315+Balshgit@users.noreply.github.com> Date: Wed, 11 Oct 2023 22:38:46 +0300 Subject: [PATCH] add style check by ruff (#34) * reformat settings * add init tests for timed lru cache * add check style by ruff --- Makefile | 6 +- README.md | 4 +- bot_microservice/constants.py | 6 ++ bot_microservice/core/bot/app.py | 2 +- bot_microservice/core/bot/commands.py | 14 ++--- bot_microservice/core/bot/repository.py | 9 +-- bot_microservice/core/bot/services.py | 9 ++- bot_microservice/core/utils.py | 6 +- bot_microservice/infra/database/db_adapter.py | 8 +-- .../versions/2023-10-05-18-28_eb78565abec7.py | 2 +- .../versions/2023-10-05-20-44_c2e443941930.py | 2 +- bot_microservice/settings/config.py | 9 +-- .../tests/integration/factories/bot.py | 6 +- bot_microservice/tests/unit/__init__.py | 0 .../tests/unit/test_system_utils.py | 60 +++++++++++++++++++ lefthook.yml | 6 ++ poetry.lock | 34 +++++++++-- pyproject.toml | 42 ++++++++++++- 18 files changed, 182 insertions(+), 43 deletions(-) create mode 100644 bot_microservice/tests/unit/__init__.py create mode 100644 bot_microservice/tests/unit/test_system_utils.py diff --git a/Makefile b/Makefile index 3e37388..9a6b1da 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,10 @@ lint-typing: lint-complexity: flake8 $(PY_TARGET_DIRS) +## Запустить линтер ruff +lint-ruff: + ruff $(PY_TARGET_DIRS) + ## Проверить зависимостей lint-deps: poetry run poetry check @@ -45,7 +49,7 @@ lint-deps: poetry run pip-audit ## Запустить все линтеры -lint: lint-typing lint-complexity check-import-sorting lint-deps +lint: lint-typing lint-complexity check-import-sorting lint-ruff lint-deps ## Show help help: diff --git a/README.md b/README.md index 1e69f38..044f153 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ python main.py ```shell cd bot_microservice - poetry run uvicorn --host 0.0.0.0 --factory main:create_app --port 8000 --reload + poetry run uvicorn --factory --host 0.0.0.0 --port 8080 --reload --workers 2 --log-level warning ``` To start on polling mode set `START_WITH_WEBHOOK` to blank @@ -79,7 +79,7 @@ gunicorn main:create_app --workers 10 --bind 0.0.0.0:8000 --worker-class uvicorn ### Run local tests: ```bash cd bot_microservice -STAGE=runtests poetry run pytest +LOCALTEST=1 STAGE=runtests poetry run pytest ``` ### Run tests in docker compose: diff --git a/bot_microservice/constants.py b/bot_microservice/constants.py index fcea1a7..47511ed 100644 --- a/bot_microservice/constants.py +++ b/bot_microservice/constants.py @@ -1,11 +1,17 @@ +from datetime import timezone from enum import StrEnum, unique +from dateutil import tz + AUDIO_SEGMENT_DURATION = 120 * 1000 API_PREFIX = "/api" CHATGPT_BASE_URI = "/backend-api/v2/conversation" INVALID_GPT_REQUEST_MESSAGES = ("Invalid request model", "return unexpected http status code") +MOSCOW_TZ = tz.gettz("Europe/Moscow") +UTC_TZ = timezone.utc + class BotStagesEnum(StrEnum): about_me = "about_me" diff --git a/bot_microservice/core/bot/app.py b/bot_microservice/core/bot/app.py index a5c6c96..3d2fdf8 100644 --- a/bot_microservice/core/bot/app.py +++ b/bot_microservice/core/bot/app.py @@ -45,7 +45,7 @@ class BotApplication: async def polling(self) -> None: if self.settings.STAGE == "runtests": - return None + return await self.application.initialize() await self.application.start() await self.application.updater.start_polling() # type: ignore diff --git a/bot_microservice/core/bot/commands.py b/bot_microservice/core/bot/commands.py index 880453c..ebe4d3c 100644 --- a/bot_microservice/core/bot/commands.py +++ b/bot_microservice/core/bot/commands.py @@ -23,7 +23,7 @@ async def main_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> st async def about_me(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_message: - return None + return await update.effective_message.reply_text( "Автор бота: *Дмитрий Афанасьев*\n\nTg nickname: *Balshtg*", parse_mode="MarkdownV2" ) @@ -31,7 +31,7 @@ async def about_me(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def about_bot(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_message: - return None + return chatgpt_service = ChatGptService.build() model = await chatgpt_service.get_current_chatgpt_model() await update.effective_message.reply_text( @@ -44,7 +44,7 @@ async def about_bot(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: async def website(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.effective_message: - return None + return website = urljoin(settings.DOMAIN, f"{settings.chat_prefix}/") await update.effective_message.reply_text(f"Веб версия: {website}") @@ -53,7 +53,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No """Send a message when the command /help is issued.""" if not update.effective_message: - return None + return reply_markup = InlineKeyboardMarkup(main_keyboard) await update.effective_message.reply_text( "Help!", @@ -65,7 +65,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No async def ask_question(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.message: - return None + return await update.message.reply_text("Пожалуйста подождите, ответ в среднем занимает 10-15 секунд") @@ -77,10 +77,10 @@ async def ask_question(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No async def voice_recognize(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.message: - return None + return await update.message.reply_text("Пожалуйста, ожидайте :)\nТрехминутная запись обрабатывается примерно 30 секунд") if not update.message.voice: - return None + return sound_file = await update.message.voice.get_file() sound_bytes = await sound_file.download_as_bytearray() diff --git a/bot_microservice/core/bot/repository.py b/bot_microservice/core/bot/repository.py index 2c2d328..95aff98 100644 --- a/bot_microservice/core/bot/repository.py +++ b/bot_microservice/core/bot/repository.py @@ -70,15 +70,16 @@ class ChatGPTRepository: status = response.status_code for message in INVALID_GPT_REQUEST_MESSAGES: if message in response.text: - message = f"{message}: {chatgpt_model}" - logger.info(message, question=question, chatgpt_model=chatgpt_model) - return message + invalid_model_message = f"{message}: {chatgpt_model}" + logger.info(invalid_model_message, question=question, chatgpt_model=chatgpt_model) + return invalid_model_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) + else: + return response.text return "Вообще всё сломалось :(" async def request_to_chatgpt_microservice(self, question: str, chatgpt_model: str) -> Response: diff --git a/bot_microservice/core/bot/services.py b/bot_microservice/core/bot/services.py index edf7f68..3a6a44a 100644 --- a/bot_microservice/core/bot/services.py +++ b/bot_microservice/core/bot/services.py @@ -1,5 +1,5 @@ import os -import subprocess # noqa +import subprocess # noqa: S404 import tempfile from concurrent.futures.thread import ThreadPoolExecutor from dataclasses import dataclass @@ -68,7 +68,7 @@ class SpeechToTextService: new_filename = self.filename + ".wav" cmd = ["ffmpeg", "-loglevel", "quiet", "-i", self.filename, "-vn", new_filename] try: - subprocess.run(args=cmd) # noqa: S603 + subprocess.run(args=cmd, check=True) # 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) @@ -80,11 +80,10 @@ class SpeechToTextService: with AudioFile(tmpfile) as source: audio_text = self.recognizer.listen(source) try: - text = self.recognizer.recognize_google(audio_text, language="ru-RU") - return text + return self.recognizer.recognize_google(audio_text, language="ru-RU") except SpeechRecognizerError as error: logger.error("error recognizing text with google", error=error) - raise error + raise @dataclass diff --git a/bot_microservice/core/utils.py b/bot_microservice/core/utils.py index 2c97406..9b428aa 100644 --- a/bot_microservice/core/utils.py +++ b/bot_microservice/core/utils.py @@ -3,6 +3,8 @@ from functools import cache, wraps from inspect import cleandoc from typing import Any, Callable +from constants import MOSCOW_TZ + def timed_lru_cache( microseconds: int = 0, @@ -15,14 +17,14 @@ def timed_lru_cache( update_delta = timedelta( microseconds=microseconds, milliseconds=milliseconds, seconds=seconds, minutes=minutes, hours=hours ) - next_update = datetime.utcnow() + update_delta + next_update = datetime.now(tz=MOSCOW_TZ) + update_delta cached_func = cache(func) @wraps(func) def _wrapped(*args: Any, **kwargs: Any) -> Callable[[Any], Any]: nonlocal next_update - now = datetime.utcnow() + now = datetime.now(tz=MOSCOW_TZ) if now >= next_update: cached_func.cache_clear() next_update = now + update_delta diff --git a/bot_microservice/infra/database/db_adapter.py b/bot_microservice/infra/database/db_adapter.py index 7fb787c..be27f85 100644 --- a/bot_microservice/infra/database/db_adapter.py +++ b/bot_microservice/infra/database/db_adapter.py @@ -44,9 +44,9 @@ class Database: session: Session = self._sync_session_factory() try: return session - except Exception as err: + except Exception: session.rollback() - raise err + raise finally: session.commit() session.close() @@ -71,9 +71,9 @@ class Database: async with self._async_session_factory() as session, session.begin(): try: yield session - except Exception as error: + except Exception: await session.rollback() - raise error + raise async def create_database(self) -> None: """ 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 index a977a8b..adb6379 100644 --- 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 @@ -1,7 +1,7 @@ """initial commit Revision ID: eb78565abec7 -Revises: +Revises: Create Date: 2023-10-05 18:28:30.915361 """ 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 index 7d608f5..05c18e4 100644 --- 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 @@ -30,7 +30,7 @@ def upgrade() -> None: models = results.scalars().all() if models: - return None + return models = [] for model in ChatGptModelsEnum.values(): priority = 0 if model != "gpt-3.5-turbo-stream-FreeGpt" else 1 diff --git a/bot_microservice/settings/config.py b/bot_microservice/settings/config.py index 8a00715..958bede 100644 --- a/bot_microservice/settings/config.py +++ b/bot_microservice/settings/config.py @@ -1,4 +1,4 @@ -from functools import cached_property, lru_cache +from functools import cache, cached_property from os import environ from pathlib import Path from typing import Any @@ -71,9 +71,11 @@ class AppSettings(SentrySettings, LoggingSettings, BaseSettings): # Enable uvicorn reloading RELOAD: bool = False + # telegram settings TELEGRAM_API_TOKEN: str = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890" - # webhook settings START_WITH_WEBHOOK: bool = False + + # domain settings DOMAIN: str = "https://localhost" URL_PREFIX: str = "" CHAT_PREFIX: str = "" @@ -82,7 +84,6 @@ class AppSettings(SentrySettings, LoggingSettings, BaseSettings): DB_ECHO: bool = False # ==== gpt settings ==== - GPT_MODEL: str = "gpt-3.5-turbo-stream-DeepAi" GPT_BASE_HOST: str = "http://chathpt_chat_service:8858" @model_validator(mode="before") # type: ignore[arg-type] @@ -146,7 +147,7 @@ class AppSettings(SentrySettings, LoggingSettings, BaseSettings): case_sensitive = True -@lru_cache(maxsize=None) +@cache def get_settings() -> AppSettings: return AppSettings() diff --git a/bot_microservice/tests/integration/factories/bot.py b/bot_microservice/tests/integration/factories/bot.py index bce81ea..472efff 100644 --- a/bot_microservice/tests/integration/factories/bot.py +++ b/bot_microservice/tests/integration/factories/bot.py @@ -97,8 +97,7 @@ class BotMessageFactory(factory.DictFactory): @classmethod def create_instance(cls, **kwargs: Any) -> dict[str, Any]: - data = {**cls.build(**kwargs), "from": BotUserFactory()._asdict()} - return data + return {**cls.build(**kwargs), "from": BotUserFactory()._asdict()} class BotUpdateFactory(factory.DictFactory): @@ -114,8 +113,7 @@ class CallBackFactory(factory.DictFactory): @classmethod def create_instance(cls, **kwargs: Any) -> dict[str, Any]: - data = {**cls.build(**kwargs), "from": BotUserFactory()._asdict()} - return data + return {**cls.build(**kwargs), "from": BotUserFactory()._asdict()} class BotCallBackQueryFactory(factory.DictFactory): diff --git a/bot_microservice/tests/unit/__init__.py b/bot_microservice/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot_microservice/tests/unit/test_system_utils.py b/bot_microservice/tests/unit/test_system_utils.py new file mode 100644 index 0000000..6780011 --- /dev/null +++ b/bot_microservice/tests/unit/test_system_utils.py @@ -0,0 +1,60 @@ +import time +from typing import Callable + +from core.utils import timed_lru_cache + + +class TestTimedLruCache: + call_count: int = 0 + + def sum_two_numbers(self, first: int, second: int) -> int: + self.call_count += 1 + return first + second + + def test_timed_lru_cache_cached_for_1_second(self) -> None: + self.call_count = 0 + + tested_func = timed_lru_cache(seconds=1)(self.sum_two_numbers) + + self._call_function_many_times(call_times=2, func=tested_func, first=2, second=40, result=42) + time.sleep(0.5) + self._call_function_many_times(call_times=4, func=tested_func, first=2, second=40, result=42) + time.sleep(1) + self._call_function_many_times(call_times=3, func=tested_func, first=2, second=40, result=42) + assert tested_func(2, 2) == 4 + assert self.call_count == 3 + + def test_timed_lru_cache_cached_for_long_time(self) -> None: + self.call_count = 0 + + tested_func = timed_lru_cache(minutes=5)(self.sum_two_numbers) + + self._call_function_many_times(call_times=3, func=tested_func, first=2, second=40, result=42) + time.sleep(0.2) + self._call_function_many_times(call_times=4, func=tested_func, first=2, second=40, result=42) + time.sleep(0.2) + self._call_function_many_times(call_times=2, func=tested_func, first=2, second=40, result=42) + assert tested_func(2, 2) == 4 + assert self.call_count == 2 + + def test_timed_lru_cache_cached_for_short_time(self) -> None: + self.call_count = 0 + + tested_func = timed_lru_cache(milliseconds=200)(self.sum_two_numbers) + + self._call_function_many_times(call_times=2, func=tested_func, first=2, second=40, result=42) + time.sleep(0.3) + self._call_function_many_times(call_times=5, func=tested_func, first=2, second=40, result=42) + time.sleep(0.3) + self._call_function_many_times(call_times=7, func=tested_func, first=2, second=40, result=42) + time.sleep(0.3) + self._call_function_many_times(call_times=3, func=tested_func, first=2, second=40, result=42) + assert tested_func(2, 2) == 4 + assert self.call_count == 5 + + @staticmethod + def _call_function_many_times( + call_times: int, func: Callable[[int, int], int], first: int, second: int, result: int + ) -> None: + for _ in range(call_times): + assert func(first, second) == result diff --git a/lefthook.yml b/lefthook.yml index 8f876f8..0c40d99 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -45,6 +45,9 @@ format: 4_black_check: glob: "*.py" run: black --check -S {staged_files} + 5_ruff: + glob: "*.py" + run: ruff bot_microservice lint: parallel: true @@ -55,6 +58,9 @@ lint: flake8: glob: "*.py" run: flake8 bot_microservice + ruff: + glob: "*.py" + run: ruff bot_microservice check-format: parallel: true diff --git a/poetry.lock b/poetry.lock index 938a9af..a6c3577 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2839,6 +2839,32 @@ files = [ {file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"}, ] +[[package]] +name = "ruff" +version = "0.0.292" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.292-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:02f29db018c9d474270c704e6c6b13b18ed0ecac82761e4fcf0faa3728430c96"}, + {file = "ruff-0.0.292-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:69654e564342f507edfa09ee6897883ca76e331d4bbc3676d8a8403838e9fade"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c3c91859a9b845c33778f11902e7b26440d64b9d5110edd4e4fa1726c41e0a4"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4476f1243af2d8c29da5f235c13dca52177117935e1f9393f9d90f9833f69e4"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:be8eb50eaf8648070b8e58ece8e69c9322d34afe367eec4210fdee9a555e4ca7"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9889bac18a0c07018aac75ef6c1e6511d8411724d67cb879103b01758e110a81"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6bdfabd4334684a4418b99b3118793f2c13bb67bf1540a769d7816410402a205"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7c77c53bfcd75dbcd4d1f42d6cabf2485d2e1ee0678da850f08e1ab13081a8"}, + {file = "ruff-0.0.292-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e087b24d0d849c5c81516ec740bf4fd48bf363cfb104545464e0fca749b6af9"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f160b5ec26be32362d0774964e218f3fcf0a7da299f7e220ef45ae9e3e67101a"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ac153eee6dd4444501c4bb92bff866491d4bfb01ce26dd2fff7ca472c8df9ad0"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87616771e72820800b8faea82edd858324b29bb99a920d6aa3d3949dd3f88fb0"}, + {file = "ruff-0.0.292-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b76deb3bdbea2ef97db286cf953488745dd6424c122d275f05836c53f62d4016"}, + {file = "ruff-0.0.292-py3-none-win32.whl", hash = "sha256:e854b05408f7a8033a027e4b1c7f9889563dd2aca545d13d06711e5c39c3d003"}, + {file = "ruff-0.0.292-py3-none-win_amd64.whl", hash = "sha256:f27282bedfd04d4c3492e5c3398360c9d86a295be00eccc63914438b4ac8a83c"}, + {file = "ruff-0.0.292-py3-none-win_arm64.whl", hash = "sha256:7f67a69c8f12fbc8daf6ae6d36705037bde315abf8b82b6e1f4c9e74eb750f68"}, + {file = "ruff-0.0.292.tar.gz", hash = "sha256:1093449e37dd1e9b813798f6ad70932b57cf614e5c2b5c51005bf67d55db33ac"}, +] + [[package]] name = "safety" version = "2.4.0b1" @@ -2867,13 +2893,13 @@ gitlab = ["python-gitlab (>=1.3.0)"] [[package]] name = "sentry-sdk" -version = "1.31.0" +version = "1.32.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.31.0.tar.gz", hash = "sha256:6de2e88304873484207fed836388e422aeff000609b104c802749fd89d56ba5b"}, - {file = "sentry_sdk-1.31.0-py2.py3-none-any.whl", hash = "sha256:64a7141005fb775b9db298a30de93e3b83e0ddd1232dc6f36eb38aebc1553291"}, + {file = "sentry-sdk-1.32.0.tar.gz", hash = "sha256:935e8fbd7787a3702457393b74b13d89a5afb67185bc0af85c00cb27cbd42e7c"}, + {file = "sentry_sdk-1.32.0-py2.py3-none-any.whl", hash = "sha256:eeb0b3550536f3bbc05bb1c7e0feb3a78d74acb43b607159a606ed2ec0a33a4d"}, ] [package.dependencies] @@ -3491,4 +3517,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "3c1791bb01a98ef620fd9be9cd2bfda3537449c4dadd52512e99574804d2cbf7" +content-hash = "6680823e54023a1bea0652422167d179651dcdaa63b72ed1d708490605ff5e1a" diff --git a/pyproject.toml b/pyproject.toml index 5702aa1..ae6b955 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,8 @@ flake8-comments = "^0.1" flake8-newspaper-style = "^0.4" Flake8-pyproject = "^1.2.3" +ruff = "^0.0.292" + [tool.flake8] inline-quotes = "double" max-line-length = 120 @@ -120,10 +122,10 @@ ignore = [ "B008" ] per-file-ignores = [ - # too complex queries "bot_microservice/tests/*: S101", "bot_microservice/tests/integration/conftest.py: NEW100", - "bot_microservice/settings/config.py: S104" + "bot_microservice/settings/config.py: S104", + "bot_microservice/tests/unit/test_system_utils.py: S101, AAA01" ] [tool.autoflake] @@ -213,4 +215,38 @@ addopts = ''' --cov-config=.coveragerc --cov-context=test --no-cov -''' \ No newline at end of file +''' + + +[tool.ruff] +extend-select = ["F", "I", "PL", "E", "W", "C4", "PT", "B", "T10", "SIM", "TID", "T20", "PGH", "S", "RET", "ERA", "PIE", "UP", "ASYNC", "ISC", "PERF", "DTZ", "TRY", "C90"] +ignore = ["S105", "S106", "PGH003", "TRY003", "TRY004", "PT001", "PT023", "I001"] +line-length = 120 +format="grouped" + +[tool.ruff.per-file-ignores] +"bot_microservice/tests/*" = ["S101", "PLR2004", "PLR0913"] +"bot_microservice/settings/config.py" = ["S104"] + +[tool.ruff.pylint] +max-args = 15 + +[tool.ruff.flake8-bugbear] +# Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. +extend-immutable-calls = [ + "fastapi.Depends", "fastapi.Query", "fastapi.Body", "fastapi.File", "fastapi.Cookie", "fastapi.HTTPBearer", + "fastapi.Header", "fastapi.Security", "fastapi.Path", "app.api.versioning.APIVersioning", "app.api.openapi.clean_doc", + "fastapi.Form"] + +[tool.ruff.flake8-pytest-style] +parametrize-names-type = "csv" + +[tool.ruff.mccabe] +max-complexity = 15 + +[tool.ruff.isort] +force-wrap-aliases = true +combine-as-imports = true + +[tool.ruff.flake8-quotes] +inline-quotes = "double" \ No newline at end of file