Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain/plain/views/base.py: 29%
59 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
3from plain.http import (
4 HttpRequest,
5 JsonResponse,
6 Response,
7 ResponseBase,
8 ResponseNotAllowed,
9)
10from plain.utils.decorators import classonlymethod
12from .exceptions import ResponseException
14logger = logging.getLogger("plain.request")
17class View:
18 request: HttpRequest
19 url_args: tuple
20 url_kwargs: dict
22 def __init__(self, *args, **kwargs) -> None:
23 # Views can customize their init, which receives
24 # the args and kwargs from as_view()
25 pass
27 def setup(self, request: HttpRequest, *args, **kwargs) -> None:
28 if hasattr(self, "get") and not hasattr(self, "head"):
29 self.head = self.get
31 self.request = request
32 self.url_args = args
33 self.url_kwargs = kwargs
35 @classonlymethod
36 def as_view(cls, *init_args, **init_kwargs):
37 def view(request, *args, **kwargs):
38 v = cls(*init_args, **init_kwargs)
39 v.setup(request, *args, **kwargs)
40 try:
41 return v.get_response()
42 except ResponseException as e:
43 return e.response
45 # Copy possible attributes set by decorators, e.g. @csrf_exempt, from
46 # the dispatch method.
47 view.__dict__.update(cls.get_response.__dict__)
48 view.view_class = cls
50 return view
52 def get_request_handler(self) -> callable:
53 """Return the handler for the current request method."""
55 if not self.request.method:
56 raise AttributeError("HTTP method is not set")
58 handler = getattr(self, self.request.method.lower(), None)
60 if not handler:
61 logger.warning(
62 "Method Not Allowed (%s): %s",
63 self.request.method,
64 self.request.path,
65 extra={"status_code": 405, "request": self.request},
66 )
67 raise ResponseException(ResponseNotAllowed(self._allowed_methods()))
69 return handler
71 def get_response(self) -> ResponseBase:
72 handler = self.get_request_handler()
74 result = handler()
76 if isinstance(result, ResponseBase):
77 return result
79 if isinstance(result, str):
80 return Response(result)
82 if isinstance(result, list):
83 return JsonResponse(result, safe=False)
85 if isinstance(result, dict):
86 return JsonResponse(result)
88 if isinstance(result, int):
89 return Response(status=result)
91 # Allow tuple for (status_code, content)?
93 raise ValueError(f"Unexpected view return type: {type(result)}")
95 def options(self) -> Response:
96 """Handle responding to requests for the OPTIONS HTTP verb."""
97 response = Response()
98 response.headers["Allow"] = ", ".join(self._allowed_methods())
99 response.headers["Content-Length"] = "0"
100 return response
102 def _allowed_methods(self) -> list[str]:
103 known_http_method_names = [
104 "get",
105 "post",
106 "put",
107 "patch",
108 "delete",
109 "head",
110 "options",
111 "trace",
112 ]
113 return [m.upper() for m in known_http_method_names if hasattr(self, m)]