From fbdadf64b3b6984bd7acc71345db1ff0142be9ae Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Wed, 25 May 2022 12:36:05 +0300 Subject: [PATCH 1/3] Add Request contextvars --- sanic/request.py | 12 +++++++++++- tests/test_request.py | 16 +++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/sanic/request.py b/sanic/request.py index 5405de0b4a..f55283c3e7 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -1,5 +1,6 @@ from __future__ import annotations +from contextvars import ContextVar from typing import ( TYPE_CHECKING, Any, @@ -35,7 +36,7 @@ from sanic.compat import CancelledErrors, Header from sanic.constants import DEFAULT_HTTP_CONTENT_TYPE -from sanic.exceptions import BadRequest, BadURL, ServerError +from sanic.exceptions import BadRequest, BadURL, SanicException, ServerError from sanic.headers import ( AcceptContainer, Options, @@ -82,6 +83,8 @@ class Request: Properties of an HTTP request such as URL, headers, etc. """ + _current: ContextVar[Request] = ContextVar("request") + __slots__ = ( "__weakref__", "_cookies", @@ -174,6 +177,13 @@ def __repr__(self): class_name = self.__class__.__name__ return f"<{class_name}: {self.method} {self.path}>" + @classmethod + def get_current(cls) -> Request: + request = cls._current.get(None) + if not request: + raise SanicException("No current request") + return request + @classmethod def generate_id(*_): return uuid.uuid4() diff --git a/tests/test_request.py b/tests/test_request.py index 83e2f8e613..cb68325f46 100644 --- a/tests/test_request.py +++ b/tests/test_request.py @@ -4,7 +4,7 @@ import pytest from sanic import Sanic, response -from sanic.exceptions import BadURL +from sanic.exceptions import BadURL, SanicException from sanic.request import Request, uuid from sanic.server import HttpProtocol @@ -217,3 +217,17 @@ async def get(request): assert request.scope is not None assert request.scope["method"].lower() == "get" assert request.scope["path"].lower() == "/" + + +def test_cannot_get_request_outside_of_cycle(): + with pytest.raises(SanicException, match="No current request"): + Request.get_current() + + +def test_get_current_request(app): + @app.get("/") + async def get(request): + return response.json({"same": request is Request.get_current()}) + + _, resp = app.test_client.get("/") + assert resp.json["same"] From d4b1fd01b7ce675e897884ffe65d14caa862af5e Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 16 Jun 2022 20:19:46 +0300 Subject: [PATCH 2/3] Add missing contextvar setter --- sanic/request.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sanic/request.py b/sanic/request.py index f55283c3e7..999d0a4bb0 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -173,6 +173,8 @@ def __init__( self._protocol = None self.responded: bool = False + Request._current.set(self) + def __repr__(self): class_name = self.__class__.__name__ return f"<{class_name}: {self.method} {self.path}>" From 8cd81426cad02d4c9e080f45db8c5d0be654cff4 Mon Sep 17 00:00:00 2001 From: Adam Hopkins Date: Thu, 16 Jun 2022 21:49:24 +0300 Subject: [PATCH 3/3] Move location of context setter --- sanic/http.py | 1 + sanic/request.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/sanic/http.py b/sanic/http.py index 330732b2e9..b63e243d3c 100644 --- a/sanic/http.py +++ b/sanic/http.py @@ -265,6 +265,7 @@ async def http1_request_header(self): # no cov transport=self.protocol.transport, app=self.protocol.app, ) + self.protocol.request_class._current.set(request) await self.dispatch( "http.lifecycle.request", inline=True, diff --git a/sanic/request.py b/sanic/request.py index 999d0a4bb0..f55283c3e7 100644 --- a/sanic/request.py +++ b/sanic/request.py @@ -173,8 +173,6 @@ def __init__( self._protocol = None self.responded: bool = False - Request._current.set(self) - def __repr__(self): class_name = self.__class__.__name__ return f"<{class_name}: {self.method} {self.path}>"