Не разберусь, как заблокировать на доступ конкретный ключ в Redis.
У меня такой код:
from redis.asyncio import Redis
from httpx import AsyncClient, Auth
def get_redis_client():
return Redis(host=settings.redis_host, port=settings.redis_port)
class BearerAuth(Auth):
esm_url_get_token: str = urljoin(settings.esm_host, settings.esm_path_url_get_token)
token_redis_key = "esm:token"
token_expires_redis_key = "esm:token:expires"
async def _get_token(self) -> Response:
redis_client = get_redis_client()
async with redis_client.lock(self.token_redis_key):
if expires := await redis_client.get(self.token_expires_redis_key):
expires = int(expires)
if time.time() < expires:
return await redis_client.get(self.token_redis_key)
timeout = Timeout(settings.esm_timeout, read=None)
client = AsyncClient(verify=settings.esm_verify, timeout=timeout)
data = {"login": settings.esm_login, "password": settings.esm_password}
response = await client.post(url=self.esm_url_get_token, data=data)
token = response.json()["token"]
await redis_client.set(self.token_redis_key, token)
await redis_client.set(self.token_expires_redis_key, self._get_expires())
await asyncio.sleep(settings.esm_delay_between_get_token_and_request)
return token
def _get_expires(self):
return int(time.time()) + 55 * 60
async def async_auth_flow(self, request: HTTPXRequest) -> typing.AsyncGenerator[HTTPXRequest, Response]: # NOSONAR
response = await self._get_token()
token = response.json()["token"]
request.headers["Authorization"] = f"Bearer {token}"
yield request
Можно видеть, что я пытаюсь заблокировать ключ "esm:token". Нужно блокировать доступ к ключу настолько, насколько длится код в блоке контекстного менеджера. Но этот код валится на ошибке:
Traceback (most recent call last):\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 435, in run_asgi\n result = await app( # type: ignore[func-returns-value]\n
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in call\n return await self.app(scope, receive, send)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/uvicorn/middleware/message_logger.py", line 86, in call\n raise exc from None\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/uvicorn/middleware/message_logger.py", line 82, in call\n await self.app(scope, inner_receive, inner_send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/fastapi/applications.py", line 289, in call\n await super().call(scope, receive, send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/applications.py", line 122, in call\n await self.middleware_stack(scope, receive, send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in call\n raise exc\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in call\n await self.app(scope, receive, _send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/prometheus_fastapi_instrumentator/middleware.py", line 169, in call\n raise exc\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/prometheus_fastapi_instrumentator/middleware.py", line 167, in call\n await self.app(scope, receive, send_wrapper)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 108, in call\n response = await self.dispatch_func(request, call_next)\n
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/partners_logging/middlewares.py", line 24, in logger_middleware\n return await call_next(request)\n
^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 84, in call_next\n raise app_exc\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 70, in coro\n await self.app(scope, receive_or_disconnect, send_no_error)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in call\n raise exc\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in call\n await self.app(scope, receive, sender)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in call\n raise e\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in call\n await self.app(scope, receive, send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 718, in call\n await route.handle(scope, receive, send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle\n await self.app(scope, receive, send)\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 66, in app\n response = await func(request)\n
^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/fastapi/routing.py", line 273, in app\n raw_response = await run_endpoint_function(\n
^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/fastapi/routing.py", line 190, in run_endpoint_function\n return await dependant.call(**values)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/web/api/test.py", line 47, in check_lock\n await client.create_request(request, user)\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/services/integrations/client.py", line 151, in create_request\n response = await self.raw_request(action="post", url=self.esm_url_post, data=data)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/services/integrations/client.py", line 44, in wrapped\n result: Response = await func(self, *args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/services/integrations/client.py", line 141, in raw_request\n response = await getattr(client, action)(url=url, json=data)\n
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/httpx/_client.py", line 1848, in post\n return await self.request(\n
^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/httpx/_client.py", line 1533, in request\n return await self.send(request, auth=auth, follow_redirects=follow_redirects)\n
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/httpx/_client.py", line 1620, in send\n response = await self._send_handling_auth(\n
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/httpx/_client.py", line 1645, in _send_handling_auth\n request = await auth_flow.anext()\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/services/integrations/client.py", line 110, in async_auth_flow\n response = await self._get_token()\n ^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/services/integrations/client.py", line 44, in wrapped\n result: Response = await func(self, *args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File "/Users/albertaleksandrov/PycharmProjects/kz-esm/app/services/integrations/client.py", line 88, in _get_token\n async with redis_client.lock(self.token_redis_key):\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/redis/asyncio/lock.py", line 165, in aexit\n await self.release()\n File "/Users/albertaleksandrov/Library/Caches/pypoetry/virtualenvs/esm-Xqw79VLb-py3.11/lib/python3.11/site-packages/redis/asyncio/lock.py", line 262, in do_release\n raise LockNotOwnedError("Cannot release a lock that's no longer owned")\nredis.exceptions.LockNotOwnedError: Cannot release a lock that's no longer owned
Как правильно залочить ключ Redis?