Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain/plain/internal/handlers/base.py: 89%

65 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-23 11:16 -0600

1import logging 

2import types 

3 

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 

10 

11from .exception import convert_exception_to_response 

12 

13logger = logging.getLogger("plain.request") 

14 

15 

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] 

23 

24 

25class BaseHandler: 

26 _view_middleware = None 

27 _middleware_chain = None 

28 

29 def load_middleware(self): 

30 """ 

31 Populate middleware lists from settings.MIDDLEWARE. 

32 

33 Must be called after the environment is fixed (see __call__ in subclasses). 

34 """ 

35 self._view_middleware = [] 

36 

37 get_response = self._get_response 

38 handler = convert_exception_to_response(get_response) 

39 

40 middlewares = reversed(BUILTIN_MIDDLEWARE + settings.MIDDLEWARE) 

41 

42 for middleware_path in middlewares: 

43 middleware = import_string(middleware_path) 

44 mw_instance = middleware(handler) 

45 

46 if mw_instance is None: 

47 raise ImproperlyConfigured( 

48 f"Middleware factory {middleware_path} returned None." 

49 ) 

50 

51 if hasattr(mw_instance, "process_view"): 

52 self._view_middleware.insert( 

53 0, 

54 mw_instance.process_view, 

55 ) 

56 

57 handler = convert_exception_to_response(mw_instance) 

58 

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 

62 

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 

78 

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) 

87 

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 

95 

96 if response is None: 

97 response = callback(request, *callback_args, **callback_kwargs) 

98 

99 # Complain if the view returned None (a common error). 

100 self.check_response(response, callback) 

101 

102 return response 

103 

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 

120 

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 ) 

134 

135 

136def reset_urlconf(sender, **kwargs): 

137 """Reset the URLconf after each request is finished.""" 

138 set_urlconf(None) 

139 

140 

141request_finished.connect(reset_urlconf)