Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain/plain/internal/handlers/base.py: 32%
65 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
1import logging
2import types
4from plain.exceptions import ImproperlyConfigured
5from plain.logs import log_response
6from plain.runtime import settings
7from plain.signals import request_finished
8from plain.urls import get_resolver, set_urlconf
9from plain.utils.module_loading import import_string
11from .exception import convert_exception_to_response
13logger = logging.getLogger("plain.request")
16# These middleware classes are always used by Plain.
17BUILTIN_MIDDLEWARE = [
18 "plain.internal.middleware.headers.DefaultHeadersMiddleware",
19 "plain.internal.middleware.https.HttpsRedirectMiddleware",
20 "plain.internal.middleware.slash.RedirectSlashMiddleware",
21 "plain.csrf.middleware.CsrfViewMiddleware",
22]
25class BaseHandler:
26 _view_middleware = None
27 _middleware_chain = None
29 def load_middleware(self):
30 """
31 Populate middleware lists from settings.MIDDLEWARE.
33 Must be called after the environment is fixed (see __call__ in subclasses).
34 """
35 self._view_middleware = []
37 get_response = self._get_response
38 handler = convert_exception_to_response(get_response)
40 middlewares = reversed(BUILTIN_MIDDLEWARE + settings.MIDDLEWARE)
42 for middleware_path in middlewares:
43 middleware = import_string(middleware_path)
44 mw_instance = middleware(handler)
46 if mw_instance is None:
47 raise ImproperlyConfigured(
48 f"Middleware factory {middleware_path} returned None."
49 )
51 if hasattr(mw_instance, "process_view"):
52 self._view_middleware.insert(
53 0,
54 mw_instance.process_view,
55 )
57 handler = convert_exception_to_response(mw_instance)
59 # We only assign to this when initialization is complete as it is used
60 # as a flag for initialization being complete.
61 self._middleware_chain = handler
63 def get_response(self, request):
64 """Return a Response object for the given HttpRequest."""
65 # Setup default url resolver for this thread
66 set_urlconf(settings.ROOT_URLCONF)
67 response = self._middleware_chain(request)
68 response._resource_closers.append(request.close)
69 if response.status_code >= 400:
70 log_response(
71 "%s: %s",
72 response.reason_phrase,
73 request.path,
74 response=response,
75 request=request,
76 )
77 return response
79 def _get_response(self, request):
80 """
81 Resolve and call the view, then apply view, exception, and
82 template_response middleware. This method is everything that happens
83 inside the request/response middleware.
84 """
85 response = None
86 callback, callback_args, callback_kwargs = self.resolve_request(request)
88 # Apply view middleware
89 for middleware_method in self._view_middleware:
90 response = middleware_method(
91 request, callback, callback_args, callback_kwargs
92 )
93 if response:
94 break
96 if response is None:
97 response = callback(request, *callback_args, **callback_kwargs)
99 # Complain if the view returned None (a common error).
100 self.check_response(response, callback)
102 return response
104 def resolve_request(self, request):
105 """
106 Retrieve/set the urlconf for the request. Return the view resolved,
107 with its args and kwargs.
108 """
109 # Work out the resolver.
110 if hasattr(request, "urlconf"):
111 urlconf = request.urlconf
112 set_urlconf(urlconf)
113 resolver = get_resolver(urlconf)
114 else:
115 resolver = get_resolver()
116 # Resolve the view, and assign the match object back to the request.
117 resolver_match = resolver.resolve(request.path_info)
118 request.resolver_match = resolver_match
119 return resolver_match
121 def check_response(self, response, callback, name=None):
122 """
123 Raise an error if the view returned None or an uncalled coroutine.
124 """
125 if not name:
126 if isinstance(callback, types.FunctionType): # FBV
127 name = f"The view {callback.__module__}.{callback.__name__}"
128 else: # CBV
129 name = f"The view {callback.__module__}.{callback.__class__.__name__}.__call__"
130 if response is None:
131 raise ValueError(
132 f"{name} didn't return a Response object. It returned None " "instead."
133 )
136def reset_urlconf(sender, **kwargs):
137 """Reset the URLconf after each request is finished."""
138 set_urlconf(None)
141request_finished.connect(reset_urlconf)