-
Andrey Azeev authored
Подключена lib-aio-redis v1.1.0 (P14/P5 OK): - RedisSettings (host/port/password/db/socket_*/max_connections/ health_check_interval), все OPTIONAL с дефолтами localhost:6379. - src/utils/redis_client.py — canon lifespan-обёртка redis_lifespan() -> AsyncIterator[AsyncRedis | None] + get_redis + RedisDep. При RedisConnectionError на старте yield None — приложение продолжает работать с per-worker in-memory кэшем. - app.py lifespan: redis_client = enter_async_context(redis_lifespan()) → app.state.redis. SokolovApi(redis=redis_client) после Redis. SokolovApi token cache (Redis-first, in-memory fallback): - Ключ "sokolov:access_token", TTL 23h (Sokolov docs: 1 день, с запасом). - _get_access_token(stale_token=None): cache → Redis → login под Lock. - Stale-token aware refresh: на 401 в _get_access_token(stale_token=T) под локом проверяем self._token != T и Redis != T — если кто-то уже обновил, login не повторяется. Защита от thundering herd при N параллельных 401. - parse_json_response из lib_aiohttp для login + get_products. SokolovApi fixes: - BaseAPI(__init__) больше не получает headers= (default headers перекрывали per-call Bearer для products). Headers теперь per-call. - _basic_auth_headers/_bearer_headers — instance methods (раньше classmethod без args — login был сломан). - get_products(refresh_on_401: bool = True) — новый параметр для контроля retry поведения. Batch endpoint GET /api/sokolov/product?sku=...&sku=... (до 10): - Old GET /api/sokolov/product/{vendor_code} удалён. - response_model=dict[str, Product | None]. - SokolovService.get_products_by_skus: 1) dict.fromkeys дедуп СНАЧАЛА (сохраняет порядок первого появления); 2) первый sku sequential (refresh_on_401=True) — валидирует токен для всего батча; если None → 502; 3) остальные через asyncio.gather (refresh_on_401=False) — на 401 просто None для slot, никакого login-storm на upstream; 4) return_exceptions=True — упавший слот → None, не валит весь batch. - _build_product: complectProducts=attributes.get("complect-products") or [] (был ValidationError на None). Health endpoint полностью удалён (override от пользователя): - src/routers/health/, src/settings/health_settings.py git rm. - src/guard/__init__.py: verify_api_health_token убран из __all__. - src/routers/__init__.py: больше нет health_router. - env.template / .env.example: убран блок APP__HEALTHCHECK_*. - tests/conftest.py: убран APP__HEALTHCHECK_TOKEN default. - tests/test_app_startup.py: RequestIdMiddleware тесты на 404-пути NO_AUTH_PATH (auth_headers fixture больше не нужен). CI deploy переменные суффиксированы _NODE3 (gitlab-ci.yml + README): SSH_PRIVATE_KEY_NODE3, DEPLOY_HOST_NODE3, DEPLOY_USER_NODE3, DEPLOY_PORT_NODE3. Sokolov client paths приведены в соответствие docs: - host = "https://api.b2b.sokolov.net" (linter временно ставил https://b2b.sokolov.ru — это не API endpoint). - login path /ru-ru/login (был баг /login vs /ru-ru/login). tests/unit/test_sokolov_service.py (7 кейсов): - empty list → {} - single sku → 1 call - 3 skus → 3 calls (first sequential + rest gather) - dedup preserves order ["A","B","A"] → 2 calls - first None → 502 raised, gather не стартует - parallel exception → None для slot - empty data → None Verification: - poetry run ruff check → All checks passed - poetry run pytest → 11 passed, coverage 86.06% ≥ 80% - poetry check → All set! - grep "git.web.luc.kz" pyproject.toml | grep -v "tag = " → пусто (P5) - 5 git-либ с tag (lib-aiohttp, lib-utils, lib-auth-guard, lucente-observability, lib-aio-redis)3501eb07Andrey Azeev authoredПодключена lib-aio-redis v1.1.0 (P14/P5 OK): - RedisSettings (host/port/password/db/socket_*/max_connections/ health_check_interval), все OPTIONAL с дефолтами localhost:6379. - src/utils/redis_client.py — canon lifespan-обёртка redis_lifespan() -> AsyncIterator[AsyncRedis | None] + get_redis + RedisDep. При RedisConnectionError на старте yield None — приложение продолжает работать с per-worker in-memory кэшем. - app.py lifespan: redis_client = enter_async_context(redis_lifespan()) → app.state.redis. SokolovApi(redis=redis_client) после Redis. SokolovApi token cache (Redis-first, in-memory fallback): - Ключ "sokolov:access_token", TTL 23h (Sokolov docs: 1 день, с запасом). - _get_access_token(stale_token=None): cache → Redis → login под Lock. - Stale-token aware refresh: на 401 в _get_access_token(stale_token=T) под локом проверяем self._token != T и Redis != T — если кто-то уже обновил, login не повторяется. Защита от thundering herd при N параллельных 401. - parse_json_response из lib_aiohttp для login + get_products. SokolovApi fixes: - BaseAPI(__init__) больше не получает headers= (default headers перекрывали per-call Bearer для products). Headers теперь per-call. - _basic_auth_headers/_bearer_headers — instance methods (раньше classmethod без args — login был сломан). - get_products(refresh_on_401: bool = True) — новый параметр для контроля retry поведения. Batch endpoint GET /api/sokolov/product?sku=...&sku=... (до 10): - Old GET /api/sokolov/product/{vendor_code} удалён. - response_model=dict[str, Product | None]. - SokolovService.get_products_by_skus: 1) dict.fromkeys дедуп СНАЧАЛА (сохраняет порядок первого появления); 2) первый sku sequential (refresh_on_401=True) — валидирует токен для всего батча; если None → 502; 3) остальные через asyncio.gather (refresh_on_401=False) — на 401 просто None для slot, никакого login-storm на upstream; 4) return_exceptions=True — упавший слот → None, не валит весь batch. - _build_product: complectProducts=attributes.get("complect-products") or [] (был ValidationError на None). Health endpoint полностью удалён (override от пользователя): - src/routers/health/, src/settings/health_settings.py git rm. - src/guard/__init__.py: verify_api_health_token убран из __all__. - src/routers/__init__.py: больше нет health_router. - env.template / .env.example: убран блок APP__HEALTHCHECK_*. - tests/conftest.py: убран APP__HEALTHCHECK_TOKEN default. - tests/test_app_startup.py: RequestIdMiddleware тесты на 404-пути NO_AUTH_PATH (auth_headers fixture больше не нужен). CI deploy переменные суффиксированы _NODE3 (gitlab-ci.yml + README): SSH_PRIVATE_KEY_NODE3, DEPLOY_HOST_NODE3, DEPLOY_USER_NODE3, DEPLOY_PORT_NODE3. Sokolov client paths приведены в соответствие docs: - host = "https://api.b2b.sokolov.net" (linter временно ставил https://b2b.sokolov.ru — это не API endpoint). - login path /ru-ru/login (был баг /login vs /ru-ru/login). tests/unit/test_sokolov_service.py (7 кейсов): - empty list → {} - single sku → 1 call - 3 skus → 3 calls (first sequential + rest gather) - dedup preserves order ["A","B","A"] → 2 calls - first None → 502 raised, gather не стартует - parallel exception → None для slot - empty data → None Verification: - poetry run ruff check → All checks passed - poetry run pytest → 11 passed, coverage 86.06% ≥ 80% - poetry check → All set! - grep "git.web.luc.kz" pyproject.toml | grep -v "tag = " → пусто (P5) - 5 git-либ с tag (lib-aiohttp, lib-utils, lib-auth-guard, lucente-observability, lib-aio-redis)
Loading