From ff2b9bb8d7edaf20a6bd7bdbaa93cae79a860aa4 Mon Sep 17 00:00:00 2001 From: deadzilla Date: Wed, 8 Jul 2026 00:07:55 +0500 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5:=20factory-=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D0=B4=D0=BB=D1=8F=20RateLimiter=20(make?= =?UTF-8?q?=5Fcat=5Flimiter,=20make=5Fweather=5Flimiter=20=D0=B8=20=D1=82.?= =?UTF-8?q?=D0=B4.)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ISSUES.md | 14 +++++--------- utils/rate_limiter.py | 33 +++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/ISSUES.md b/ISSUES.md index 39b1829..9098ff9 100644 --- a/ISSUES.md +++ b/ISSUES.md @@ -13,15 +13,11 @@ ## Средний приоритет -- [ ] **Type hints в production-коде** — 17 функций без аннотаций: - - `commands/`: `cat()`, `hp()`, `morning()`, `nw()`, `pg()`, `stats()`, `status()` — все методы Cog - - `utils/pogoda.py`: `fetch_weather()`, `fetch_open_meteo()`, `wmo_to_russian()`, `translate_weather()`, `format_weather_data_for_console()`, `format_weather_for_embed()`, `pressure_to_mmhg()` - - `utils/news.py`: `fetch_rss()`, `truncate_title()`, `format_articles()` - - `utils/morning_runner.py`: `run_morning()`, `start()`, `stop()` -- [ ] **`asyncio.iscoroutinefunction` deprecated** — 53 warnings при запуске тестов, функция удалена в Python 3.16; причина — discord.py использует deprecated API (решение: обновление discord.py или monkey-patch в `bot.py`) -- [ ] **Утечка корутины Scheduler в тестах** — `RuntimeWarning: coroutine 'Scheduler._scheduler_loop' was never awaited` в `test_morning_runner.py` (строки 105, 137, 166 — моки не останавливают task) -- [ ] **Graceful shutdown (SIGTERM)** — нет обработки `SIGTERM`, бот завершается резко в Docker/K8s без закрытия handler-ов и cleanup -- [ ] **Пустой `__init__` в Morning** — `commands/morning.py` (строка 13): `def __init__(self): pass` можно убрать +- [x] **Type hints в production-коде** — добавлены аннотации ко всем 20 функциям +- [x] **`asyncio.iscoroutinefunction` deprecated** — monkey-patch в `bot.py` + `conftest.py` +- [x] **Утечка корутины Scheduler в тестах** — mock `_start_scheduler` вместо `asyncio.create_task` +- [x] **Graceful shutdown (SIGTERM)** — уже реализован через `on_shutdown` listener + `async with self.bot` +- [x] **Пустой `__init__` в Morning** — удалён - [ ] **Global RateLimiter на модульном уровне** — `utils/rate_limiter.py` (строки 70-75): экземпляры создаются при импорте, нельзя подменить в тестах ## Низкий приоритет diff --git a/utils/rate_limiter.py b/utils/rate_limiter.py index be81ac0..c95e055 100644 --- a/utils/rate_limiter.py +++ b/utils/rate_limiter.py @@ -53,7 +53,7 @@ class RateLimiter: await asyncio.sleep(token / self.rate) -# --- Готовые лимитеры по API --- +# --- Конфигурация лимитеров --- # TheCatAPI: бесплатно, 1 req/sec, burst 3 _CAT_RATE: Final[float] = float(os.getenv("CAT_API_RATE", "1")) @@ -71,8 +71,29 @@ _OPEN_METEO_BURST: Final[int] = int(os.getenv("OPEN_METEO_API_BURST", "5")) _HABR_RSS_RATE: Final[float] = float(os.getenv("HABR_RSS_RATE", "1")) _HABR_RSS_BURST: Final[int] = int(os.getenv("HABR_RSS_BURST", "2")) -# Экземпляры лимитеров -cat_limiter: RateLimiter = RateLimiter(_CAT_RATE, _CAT_BURST) -weather_limiter: RateLimiter = RateLimiter(_WEATHER_RATE, _WEATHER_BURST) -open_meteo_limiter: RateLimiter = RateLimiter(_OPEN_METEO_RATE, _OPEN_METEO_BURST) -habr_rss_limiter: RateLimiter = RateLimiter(_HABR_RSS_RATE, _HABR_RSS_BURST) + +def make_cat_limiter() -> RateLimiter: + """Создать лимитер для TheCatAPI.""" + return RateLimiter(_CAT_RATE, _CAT_BURST) + + +def make_weather_limiter() -> RateLimiter: + """Создать лимитер для wttr.in.""" + return RateLimiter(_WEATHER_RATE, _WEATHER_BURST) + + +def make_open_meteo_limiter() -> RateLimiter: + """Создать лимитер для Open-Meteo.""" + return RateLimiter(_OPEN_METEO_RATE, _OPEN_METEO_BURST) + + +def make_habr_rss_limiter() -> RateLimiter: + """Создать лимитер для Habr RSS.""" + return RateLimiter(_HABR_RSS_RATE, _HABR_RSS_BURST) + + +# Экземпляры лимитеров (глобальные, для production) +cat_limiter: RateLimiter = make_cat_limiter() +weather_limiter: RateLimiter = make_weather_limiter() +open_meteo_limiter: RateLimiter = make_open_meteo_limiter() +habr_rss_limiter: RateLimiter = make_habr_rss_limiter()