diff --git a/README.md b/README.md index f450dabe..8b78c1e6 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,8 @@ urlpatterns = winter.django.create_urls(app) - `winter.web.throttling.reset(request, scope)` is gone: an API method with `@throttling` declares a `throttle: winter.web.Throttle` argument, and `throttle.reset()` forgets what the scope has counted for the sender: for the identity that the request was counted for, even if the method has logged the user in since. +- `register_native_response_type` is gone: an adapter passes the response types of its framework to `handle_request`. + An API method may still return a Django response. - OpenAPI: `generate_openapi(app, title=..., version=...)` returns the spec and checks `@raises` annotations of the application (`allow_missing_raises_annotation=True` skips the check). `winter.openapi.serve_spec(app, spec, url='openapi.json')` and diff --git a/docs/architecture.md b/docs/architecture.md index 7b3ac7da..c4a08615 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -85,17 +85,14 @@ at least two of them. - **Framework behaviour is still process-global.** A `winter.web.WebApplication` holds the code of a web application, its injector and its interceptors. The adapters derive its exception handlers from its definitions, handle each of its requests in a child of its injector and throttle in the - storage that its injector binds. The other behavioural registries of `winter.web` and - `winter.openapi` (native response types, OpenAPI inspectors) are module singletons, and - `winter.sqlalchemy`, `winter.ddd` and `winter.messaging` take the injector from the service - locator `winter.application.get_injector`. -- **Models are initialised once per process.** The Django adapter's `_initialize_process` and - `winter.openapi.initialization.initialize` fill those module singletons with the Django response type - and the standard inspectors on first use. In the - target, a `WebApplication` is given these registries like its interceptors, and they start with the - standard entries. Building from an application, an adapter adds - its own entries (the native response type) to that application only. The - `initialize` functions then disappear. + storage that its injector binds; what belongs to an adapter, such as the response types of its + framework, it passes to `handle_request` itself. The OpenAPI inspectors and global exceptions of + `winter.openapi` are module singletons, and `winter.sqlalchemy`, `winter.ddd` and + `winter.messaging` take the injector from the service locator `winter.application.get_injector`. +- **`winter.openapi` is initialised once per process.** `winter.openapi.initialization.initialize` + fills those module singletons with the standard inspectors and global exceptions on first use. In + the target, `generate_openapi` is given them, starting with the standard entries, and `initialize` + disappears. - **`winter.wsgi`, `winter.asgi` and `winter.rabbitmq` do not exist yet.** HTTP is served through `winter.django` only, and `winter.messaging` has no transport adapter. - **All dependencies are mandatory.** Django, SQLAlchemy, Redis and pydantic are installed with diff --git a/winter/django/view.py b/winter/django/view.py index 7d6c87f2..4f60fdd9 100644 --- a/winter/django/view.py +++ b/winter/django/view.py @@ -11,7 +11,6 @@ from winter.web import Response from winter.web import WebApplication from winter.web import handle_request -from winter.web import register_native_response_type from winter.web.autodiscovery import find_routes from winter.web.exception_handlers import find_exception_handlers from winter.web.exceptions import ExceptionHandlers @@ -27,7 +26,6 @@ def create_urls(app: WebApplication) -> List[URLPattern]: """URL patterns of the routes of an application, relative to where they are included in the urlpatterns.""" - _initialize_process() routes = find_routes(app) exception_handlers = find_exception_handlers(app) # Each request binds the Django request, see _create_dispatch_function @@ -35,17 +33,6 @@ def create_urls(app: WebApplication) -> List[URLPattern]: return _create_urls_from_routes(app, routes, exception_handlers) -_process_initialized = False - - -def _initialize_process(): - """Process-global until native response types belong to an application: docs/architecture.md, Current deviations.""" - global _process_initialized - if not _process_initialized: - register_native_response_type(HttpResponseBase) - _process_initialized = True - - def _create_urls_from_routes( app: WebApplication, routes: List[Route], @@ -96,7 +83,15 @@ def _create_dispatch_function(app: WebApplication, route: Route, exception_handl def dispatch(winter_view, request: django.http.HttpRequest, **path_parameters): winter_request = DjangoRequest(request, path_parameters) request_values = {django.http.HttpRequest: request} - response = handle_request(app, route, winter_request, exception_handlers, request_values, django_user_identity) + response = handle_request( + app, + route, + winter_request, + exception_handlers, + request_values, + throttling_identity=django_user_identity, + native_response_types=(HttpResponseBase,), + ) return to_django_response(response) return dispatch diff --git a/winter/web/__init__.py b/winter/web/__init__.py index 03d382e0..94fcddd7 100644 --- a/winter/web/__init__.py +++ b/winter/web/__init__.py @@ -11,7 +11,6 @@ from .request_body_annotation import request_body from .request import Request from .request_handler import handle_request -from .request_handler import register_native_response_type from .request_scope import request_scope from .request_header_annotation import request_header from .response import Response diff --git a/winter/web/request_handler.py b/winter/web/request_handler.py index 3c4594de..abd7af5b 100644 --- a/winter/web/request_handler.py +++ b/winter/web/request_handler.py @@ -82,6 +82,7 @@ def handle_request( exception_handlers: ExceptionHandlers, request_values: Mapping[type, object], throttling_identity: IdentityResolver, + native_response_types: tuple[type, ...], ) -> Response: """ Run the whole handling pipeline for a matched route of the application: throttling, its interceptors, argument @@ -90,7 +91,8 @@ def handle_request( The request is handled in an injector of its own, a child of the application's one, that binds the Request and the request values of the adapter, such as its own request object. Request-scoped values live in it. Throttling counts the request in the storage of the application's injector for the identity that the adapter - resolves, since the adapter knows the users of its framework. + resolves, since the adapter knows the users of its framework. An API method or an exception handler may return + an object of the native response types of the adapter, which reaches the adapter untouched. """ method = route.method response_headers: dict[str, str] = {} @@ -121,7 +123,7 @@ def handle_request( method = exception_handlers.get(Exception) result = _handle_exception(method, handler_exception, request, response_headers, injector) - return _to_response(request, result, method, response_headers) + return _to_response(request, result, method, response_headers, native_response_types) def _handle_exception( @@ -137,7 +139,13 @@ def _handle_exception( return method(handler, **arguments) -def _to_response(request: Request, result: Any, method: ComponentMethod, headers: dict[str, str]) -> Response: +def _to_response( + request: Request, + result: Any, + method: ComponentMethod, + headers: dict[str, str], + native_response_types: tuple[type, ...], +) -> Response: if isinstance(result, Response): return Response(status_code=result.status_code, body=result.body, headers={**headers, **result.headers}) @@ -148,25 +156,9 @@ def _to_response(request: Request, result: Any, method: ComponentMethod, headers body = result status_code = get_response_status(request.method, method) - if _is_native(body): + if isinstance(body, native_response_types): return Response(status_code=status_code, body=body, headers=headers) headers.setdefault('content-type', 'application/json') content = b'' if body is None else json.dumps(body, cls=JSONEncoder).encode() return Response(status_code=status_code, body=content, headers=headers) - - -_native_response_types = () - - -def register_native_response_type(type_: type): - """ - Adapters register the response types of their framework here. An API method may return such an object, - and it reaches the adapter untouched. - """ - global _native_response_types - _native_response_types = _native_response_types + (type_,) - - -def _is_native(body: Any) -> bool: - return isinstance(body, _native_response_types) diff --git a/winter/web/response.py b/winter/web/response.py index b8cff448..a7501fd4 100644 --- a/winter/web/response.py +++ b/winter/web/response.py @@ -6,10 +6,10 @@ class Response: """ What winter.web hands to an adapter after handling a request, and what an API method or - exception handler returns to bypass output processing and JSON encoding. + exception handler returns to bypass JSON encoding. - `body` is the final payload as bytes, or an object native to the adapter in use - (a Django HttpResponse, for example), which the adapter sends as is. + `body` is the final payload as bytes, or an object of the native response types of the adapter + in use (a Django HttpResponse, for example), which the adapter sends as is. """ status_code: int body: Any = b''