Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/exceptions.py: 64%
239 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1"""Implements a number of Python exceptions which can be raised from within
2a view to trigger a standard HTTP non-200 response.
4Usage Example
5-------------
7.. code-block:: python
9 from werkzeug.wrappers.request import Request
10 from werkzeug.exceptions import HTTPException, NotFound
12 def view(request):
13 raise NotFound()
15 @Request.application
16 def application(request):
17 try:
18 return view(request)
19 except HTTPException as e:
20 return e
22As you can see from this example those exceptions are callable WSGI
23applications. However, they are not Werkzeug response objects. You
24can get a response object by calling ``get_response()`` on a HTTP
25exception.
27Keep in mind that you may have to pass an environ (WSGI) or scope
28(ASGI) to ``get_response()`` because some errors fetch additional
29information relating to the request.
31If you want to hook in a different exception page to say, a 404 status
32code, you can add a second except for a specific subclass of an error:
34.. code-block:: python
36 @Request.application
37 def application(request):
38 try:
39 return view(request)
40 except NotFound as e:
41 return not_found(request)
42 except HTTPException as e:
43 return e
45"""
46import typing as t
47from datetime import datetime
49from markupsafe import escape
50from markupsafe import Markup
52from ._internal import _get_environ
54if t.TYPE_CHECKING:
55 import typing_extensions as te
56 from _typeshed.wsgi import StartResponse
57 from _typeshed.wsgi import WSGIEnvironment
58 from .datastructures import WWWAuthenticate
59 from .sansio.response import Response
60 from .wrappers.request import Request as WSGIRequest # noqa: F401
61 from .wrappers.response import Response as WSGIResponse # noqa: F401
64class HTTPException(Exception):
65 """The base class for all HTTP exceptions. This exception can be called as a WSGI
66 application to render a default error page or you can catch the subclasses
67 of it independently and render nicer error messages.
69 .. versionchanged:: 2.1
70 Removed the ``wrap`` class method.
71 """
73 code: t.Optional[int] = None
74 description: t.Optional[str] = None
76 def __init__(
77 self,
78 description: t.Optional[str] = None,
79 response: t.Optional["Response"] = None,
80 ) -> None:
81 super().__init__()
82 if description is not None:
83 self.description = description
84 self.response = response
86 @property
87 def name(self) -> str:
88 """The status name."""
89 from .http import HTTP_STATUS_CODES
91 return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore
93 def get_description(
94 self,
95 environ: t.Optional["WSGIEnvironment"] = None,
96 scope: t.Optional[dict] = None,
97 ) -> str:
98 """Get the description."""
99 if self.description is None:
100 description = ""
101 elif not isinstance(self.description, str):
102 description = str(self.description)
103 else:
104 description = self.description
106 description = escape(description).replace("\n", Markup("<br>"))
107 return f"<p>{description}</p>"
109 def get_body(
110 self,
111 environ: t.Optional["WSGIEnvironment"] = None,
112 scope: t.Optional[dict] = None,
113 ) -> str:
114 """Get the HTML body."""
115 return (
116 "<!doctype html>\n"
117 "<html lang=en>\n"
118 f"<title>{self.code} {escape(self.name)}</title>\n"
119 f"<h1>{escape(self.name)}</h1>\n"
120 f"{self.get_description(environ)}\n"
121 )
123 def get_headers(
124 self,
125 environ: t.Optional["WSGIEnvironment"] = None,
126 scope: t.Optional[dict] = None,
127 ) -> t.List[t.Tuple[str, str]]:
128 """Get a list of headers."""
129 return [("Content-Type", "text/html; charset=utf-8")]
131 def get_response(
132 self,
133 environ: t.Optional[t.Union["WSGIEnvironment", "WSGIRequest"]] = None,
134 scope: t.Optional[dict] = None,
135 ) -> "Response":
136 """Get a response object. If one was passed to the exception
137 it's returned directly.
139 :param environ: the optional environ for the request. This
140 can be used to modify the response depending
141 on how the request looked like.
142 :return: a :class:`Response` object or a subclass thereof.
143 """
144 from .wrappers.response import Response as WSGIResponse # noqa: F811
146 if self.response is not None:
147 return self.response
148 if environ is not None:
149 environ = _get_environ(environ)
150 headers = self.get_headers(environ, scope)
151 return WSGIResponse(self.get_body(environ, scope), self.code, headers)
153 def __call__(
154 self, environ: "WSGIEnvironment", start_response: "StartResponse"
155 ) -> t.Iterable[bytes]:
156 """Call the exception as WSGI application.
158 :param environ: the WSGI environment.
159 :param start_response: the response callable provided by the WSGI
160 server.
161 """
162 response = t.cast("WSGIResponse", self.get_response(environ))
163 return response(environ, start_response)
165 def __str__(self) -> str:
166 code = self.code if self.code is not None else "???"
167 return f"{code} {self.name}: {self.description}"
169 def __repr__(self) -> str:
170 code = self.code if self.code is not None else "???"
171 return f"<{type(self).__name__} '{code}: {self.name}'>"
174class BadRequest(HTTPException):
175 """*400* `Bad Request`
177 Raise if the browser sends something to the application the application
178 or server cannot handle.
179 """
181 code = 400
182 description = (
183 "The browser (or proxy) sent a request that this server could "
184 "not understand."
185 )
188class BadRequestKeyError(BadRequest, KeyError):
189 """An exception that is used to signal both a :exc:`KeyError` and a
190 :exc:`BadRequest`. Used by many of the datastructures.
191 """
193 _description = BadRequest.description
194 #: Show the KeyError along with the HTTP error message in the
195 #: response. This should be disabled in production, but can be
196 #: useful in a debug mode.
197 show_exception = False
199 def __init__(self, arg: t.Optional[str] = None, *args: t.Any, **kwargs: t.Any):
200 super().__init__(*args, **kwargs)
202 if arg is None:
203 KeyError.__init__(self)
204 else:
205 KeyError.__init__(self, arg)
207 @property # type: ignore
208 def description(self) -> str:
209 if self.show_exception:
210 return (
211 f"{self._description}\n"
212 f"{KeyError.__name__}: {KeyError.__str__(self)}"
213 )
215 return self._description
217 @description.setter
218 def description(self, value: str) -> None:
219 self._description = value
222class ClientDisconnected(BadRequest):
223 """Internal exception that is raised if Werkzeug detects a disconnected
224 client. Since the client is already gone at that point attempting to
225 send the error message to the client might not work and might ultimately
226 result in another exception in the server. Mainly this is here so that
227 it is silenced by default as far as Werkzeug is concerned.
229 Since disconnections cannot be reliably detected and are unspecified
230 by WSGI to a large extent this might or might not be raised if a client
231 is gone.
233 .. versionadded:: 0.8
234 """
237class SecurityError(BadRequest):
238 """Raised if something triggers a security error. This is otherwise
239 exactly like a bad request error.
241 .. versionadded:: 0.9
242 """
245class BadHost(BadRequest):
246 """Raised if the submitted host is badly formatted.
248 .. versionadded:: 0.11.2
249 """
252class Unauthorized(HTTPException):
253 """*401* ``Unauthorized``
255 Raise if the user is not authorized to access a resource.
257 The ``www_authenticate`` argument should be used to set the
258 ``WWW-Authenticate`` header. This is used for HTTP basic auth and
259 other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate`
260 to create correctly formatted values. Strictly speaking a 401
261 response is invalid if it doesn't provide at least one value for
262 this header, although real clients typically don't care.
264 :param description: Override the default message used for the body
265 of the response.
266 :param www-authenticate: A single value, or list of values, for the
267 WWW-Authenticate header(s).
269 .. versionchanged:: 2.0
270 Serialize multiple ``www_authenticate`` items into multiple
271 ``WWW-Authenticate`` headers, rather than joining them
272 into a single value, for better interoperability.
274 .. versionchanged:: 0.15.3
275 If the ``www_authenticate`` argument is not set, the
276 ``WWW-Authenticate`` header is not set.
278 .. versionchanged:: 0.15.3
279 The ``response`` argument was restored.
281 .. versionchanged:: 0.15.1
282 ``description`` was moved back as the first argument, restoring
283 its previous position.
285 .. versionchanged:: 0.15.0
286 ``www_authenticate`` was added as the first argument, ahead of
287 ``description``.
288 """
290 code = 401
291 description = (
292 "The server could not verify that you are authorized to access"
293 " the URL requested. You either supplied the wrong credentials"
294 " (e.g. a bad password), or your browser doesn't understand"
295 " how to supply the credentials required."
296 )
298 def __init__(
299 self,
300 description: t.Optional[str] = None,
301 response: t.Optional["Response"] = None,
302 www_authenticate: t.Optional[
303 t.Union["WWWAuthenticate", t.Iterable["WWWAuthenticate"]]
304 ] = None,
305 ) -> None:
306 super().__init__(description, response)
308 from .datastructures import WWWAuthenticate
310 if isinstance(www_authenticate, WWWAuthenticate):
311 www_authenticate = (www_authenticate,)
313 self.www_authenticate = www_authenticate
315 def get_headers(
316 self,
317 environ: t.Optional["WSGIEnvironment"] = None,
318 scope: t.Optional[dict] = None,
319 ) -> t.List[t.Tuple[str, str]]:
320 headers = super().get_headers(environ, scope)
321 if self.www_authenticate:
322 headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate)
323 return headers
326class Forbidden(HTTPException):
327 """*403* `Forbidden`
329 Raise if the user doesn't have the permission for the requested resource
330 but was authenticated.
331 """
333 code = 403
334 description = (
335 "You don't have the permission to access the requested"
336 " resource. It is either read-protected or not readable by the"
337 " server."
338 )
341class NotFound(HTTPException):
342 """*404* `Not Found`
344 Raise if a resource does not exist and never existed.
345 """
347 code = 404
348 description = (
349 "The requested URL was not found on the server. If you entered"
350 " the URL manually please check your spelling and try again."
351 )
354class MethodNotAllowed(HTTPException):
355 """*405* `Method Not Allowed`
357 Raise if the server used a method the resource does not handle. For
358 example `POST` if the resource is view only. Especially useful for REST.
360 The first argument for this exception should be a list of allowed methods.
361 Strictly speaking the response would be invalid if you don't provide valid
362 methods in the header which you can do with that list.
363 """
365 code = 405
366 description = "The method is not allowed for the requested URL."
368 def __init__(
369 self,
370 valid_methods: t.Optional[t.Iterable[str]] = None,
371 description: t.Optional[str] = None,
372 response: t.Optional["Response"] = None,
373 ) -> None:
374 """Takes an optional list of valid http methods
375 starting with werkzeug 0.3 the list will be mandatory."""
376 super().__init__(description=description, response=response)
377 self.valid_methods = valid_methods
379 def get_headers(
380 self,
381 environ: t.Optional["WSGIEnvironment"] = None,
382 scope: t.Optional[dict] = None,
383 ) -> t.List[t.Tuple[str, str]]:
384 headers = super().get_headers(environ, scope)
385 if self.valid_methods:
386 headers.append(("Allow", ", ".join(self.valid_methods)))
387 return headers
390class NotAcceptable(HTTPException):
391 """*406* `Not Acceptable`
393 Raise if the server can't return any content conforming to the
394 `Accept` headers of the client.
395 """
397 code = 406
398 description = (
399 "The resource identified by the request is only capable of"
400 " generating response entities which have content"
401 " characteristics not acceptable according to the accept"
402 " headers sent in the request."
403 )
406class RequestTimeout(HTTPException):
407 """*408* `Request Timeout`
409 Raise to signalize a timeout.
410 """
412 code = 408
413 description = (
414 "The server closed the network connection because the browser"
415 " didn't finish the request within the specified time."
416 )
419class Conflict(HTTPException):
420 """*409* `Conflict`
422 Raise to signal that a request cannot be completed because it conflicts
423 with the current state on the server.
425 .. versionadded:: 0.7
426 """
428 code = 409
429 description = (
430 "A conflict happened while processing the request. The"
431 " resource might have been modified while the request was being"
432 " processed."
433 )
436class Gone(HTTPException):
437 """*410* `Gone`
439 Raise if a resource existed previously and went away without new location.
440 """
442 code = 410
443 description = (
444 "The requested URL is no longer available on this server and"
445 " there is no forwarding address. If you followed a link from a"
446 " foreign page, please contact the author of this page."
447 )
450class LengthRequired(HTTPException):
451 """*411* `Length Required`
453 Raise if the browser submitted data but no ``Content-Length`` header which
454 is required for the kind of processing the server does.
455 """
457 code = 411
458 description = (
459 "A request with this method requires a valid <code>Content-"
460 "Length</code> header."
461 )
464class PreconditionFailed(HTTPException):
465 """*412* `Precondition Failed`
467 Status code used in combination with ``If-Match``, ``If-None-Match``, or
468 ``If-Unmodified-Since``.
469 """
471 code = 412
472 description = (
473 "The precondition on the request for the URL failed positive evaluation."
474 )
477class RequestEntityTooLarge(HTTPException):
478 """*413* `Request Entity Too Large`
480 The status code one should return if the data submitted exceeded a given
481 limit.
482 """
484 code = 413
485 description = "The data value transmitted exceeds the capacity limit."
488class RequestURITooLarge(HTTPException):
489 """*414* `Request URI Too Large`
491 Like *413* but for too long URLs.
492 """
494 code = 414
495 description = (
496 "The length of the requested URL exceeds the capacity limit for"
497 " this server. The request cannot be processed."
498 )
501class UnsupportedMediaType(HTTPException):
502 """*415* `Unsupported Media Type`
504 The status code returned if the server is unable to handle the media type
505 the client transmitted.
506 """
508 code = 415
509 description = (
510 "The server does not support the media type transmitted in the request."
511 )
514class RequestedRangeNotSatisfiable(HTTPException):
515 """*416* `Requested Range Not Satisfiable`
517 The client asked for an invalid part of the file.
519 .. versionadded:: 0.7
520 """
522 code = 416
523 description = "The server cannot provide the requested range."
525 def __init__(
526 self,
527 length: t.Optional[int] = None,
528 units: str = "bytes",
529 description: t.Optional[str] = None,
530 response: t.Optional["Response"] = None,
531 ) -> None:
532 """Takes an optional `Content-Range` header value based on ``length``
533 parameter.
534 """
535 super().__init__(description=description, response=response)
536 self.length = length
537 self.units = units
539 def get_headers(
540 self,
541 environ: t.Optional["WSGIEnvironment"] = None,
542 scope: t.Optional[dict] = None,
543 ) -> t.List[t.Tuple[str, str]]:
544 headers = super().get_headers(environ, scope)
545 if self.length is not None:
546 headers.append(("Content-Range", f"{self.units} */{self.length}"))
547 return headers
550class ExpectationFailed(HTTPException):
551 """*417* `Expectation Failed`
553 The server cannot meet the requirements of the Expect request-header.
555 .. versionadded:: 0.7
556 """
558 code = 417
559 description = "The server could not meet the requirements of the Expect header"
562class ImATeapot(HTTPException):
563 """*418* `I'm a teapot`
565 The server should return this if it is a teapot and someone attempted
566 to brew coffee with it.
568 .. versionadded:: 0.7
569 """
571 code = 418
572 description = "This server is a teapot, not a coffee machine"
575class UnprocessableEntity(HTTPException):
576 """*422* `Unprocessable Entity`
578 Used if the request is well formed, but the instructions are otherwise
579 incorrect.
580 """
582 code = 422
583 description = (
584 "The request was well-formed but was unable to be followed due"
585 " to semantic errors."
586 )
589class Locked(HTTPException):
590 """*423* `Locked`
592 Used if the resource that is being accessed is locked.
593 """
595 code = 423
596 description = "The resource that is being accessed is locked."
599class FailedDependency(HTTPException):
600 """*424* `Failed Dependency`
602 Used if the method could not be performed on the resource
603 because the requested action depended on another action and that action failed.
604 """
606 code = 424
607 description = (
608 "The method could not be performed on the resource because the"
609 " requested action depended on another action and that action"
610 " failed."
611 )
614class PreconditionRequired(HTTPException):
615 """*428* `Precondition Required`
617 The server requires this request to be conditional, typically to prevent
618 the lost update problem, which is a race condition between two or more
619 clients attempting to update a resource through PUT or DELETE. By requiring
620 each client to include a conditional header ("If-Match" or "If-Unmodified-
621 Since") with the proper value retained from a recent GET request, the
622 server ensures that each client has at least seen the previous revision of
623 the resource.
624 """
626 code = 428
627 description = (
628 "This request is required to be conditional; try using"
629 ' "If-Match" or "If-Unmodified-Since".'
630 )
633class _RetryAfter(HTTPException):
634 """Adds an optional ``retry_after`` parameter which will set the
635 ``Retry-After`` header. May be an :class:`int` number of seconds or
636 a :class:`~datetime.datetime`.
637 """
639 def __init__(
640 self,
641 description: t.Optional[str] = None,
642 response: t.Optional["Response"] = None,
643 retry_after: t.Optional[t.Union[datetime, int]] = None,
644 ) -> None:
645 super().__init__(description, response)
646 self.retry_after = retry_after
648 def get_headers(
649 self,
650 environ: t.Optional["WSGIEnvironment"] = None,
651 scope: t.Optional[dict] = None,
652 ) -> t.List[t.Tuple[str, str]]:
653 headers = super().get_headers(environ, scope)
655 if self.retry_after:
656 if isinstance(self.retry_after, datetime):
657 from .http import http_date
659 value = http_date(self.retry_after)
660 else:
661 value = str(self.retry_after)
663 headers.append(("Retry-After", value))
665 return headers
668class TooManyRequests(_RetryAfter):
669 """*429* `Too Many Requests`
671 The server is limiting the rate at which this user receives
672 responses, and this request exceeds that rate. (The server may use
673 any convenient method to identify users and their request rates).
674 The server may include a "Retry-After" header to indicate how long
675 the user should wait before retrying.
677 :param retry_after: If given, set the ``Retry-After`` header to this
678 value. May be an :class:`int` number of seconds or a
679 :class:`~datetime.datetime`.
681 .. versionchanged:: 1.0
682 Added ``retry_after`` parameter.
683 """
685 code = 429
686 description = "This user has exceeded an allotted request count. Try again later."
689class RequestHeaderFieldsTooLarge(HTTPException):
690 """*431* `Request Header Fields Too Large`
692 The server refuses to process the request because the header fields are too
693 large. One or more individual fields may be too large, or the set of all
694 headers is too large.
695 """
697 code = 431
698 description = "One or more header fields exceeds the maximum size."
701class UnavailableForLegalReasons(HTTPException):
702 """*451* `Unavailable For Legal Reasons`
704 This status code indicates that the server is denying access to the
705 resource as a consequence of a legal demand.
706 """
708 code = 451
709 description = "Unavailable for legal reasons."
712class InternalServerError(HTTPException):
713 """*500* `Internal Server Error`
715 Raise if an internal server error occurred. This is a good fallback if an
716 unknown error occurred in the dispatcher.
718 .. versionchanged:: 1.0.0
719 Added the :attr:`original_exception` attribute.
720 """
722 code = 500
723 description = (
724 "The server encountered an internal error and was unable to"
725 " complete your request. Either the server is overloaded or"
726 " there is an error in the application."
727 )
729 def __init__(
730 self,
731 description: t.Optional[str] = None,
732 response: t.Optional["Response"] = None,
733 original_exception: t.Optional[BaseException] = None,
734 ) -> None:
735 #: The original exception that caused this 500 error. Can be
736 #: used by frameworks to provide context when handling
737 #: unexpected errors.
738 self.original_exception = original_exception
739 super().__init__(description=description, response=response)
742class NotImplemented(HTTPException):
743 """*501* `Not Implemented`
745 Raise if the application does not support the action requested by the
746 browser.
747 """
749 code = 501
750 description = "The server does not support the action requested by the browser."
753class BadGateway(HTTPException):
754 """*502* `Bad Gateway`
756 If you do proxying in your application you should return this status code
757 if you received an invalid response from the upstream server it accessed
758 in attempting to fulfill the request.
759 """
761 code = 502
762 description = (
763 "The proxy server received an invalid response from an upstream server."
764 )
767class ServiceUnavailable(_RetryAfter):
768 """*503* `Service Unavailable`
770 Status code you should return if a service is temporarily
771 unavailable.
773 :param retry_after: If given, set the ``Retry-After`` header to this
774 value. May be an :class:`int` number of seconds or a
775 :class:`~datetime.datetime`.
777 .. versionchanged:: 1.0
778 Added ``retry_after`` parameter.
779 """
781 code = 503
782 description = (
783 "The server is temporarily unable to service your request due"
784 " to maintenance downtime or capacity problems. Please try"
785 " again later."
786 )
789class GatewayTimeout(HTTPException):
790 """*504* `Gateway Timeout`
792 Status code you should return if a connection to an upstream server
793 times out.
794 """
796 code = 504
797 description = "The connection to an upstream server timed out."
800class HTTPVersionNotSupported(HTTPException):
801 """*505* `HTTP Version Not Supported`
803 The server does not support the HTTP protocol version used in the request.
804 """
806 code = 505
807 description = (
808 "The server does not support the HTTP protocol version used in the request."
809 )
812default_exceptions: t.Dict[int, t.Type[HTTPException]] = {}
815def _find_exceptions() -> None:
816 for obj in globals().values():
817 try:
818 is_http_exception = issubclass(obj, HTTPException)
819 except TypeError:
820 is_http_exception = False
821 if not is_http_exception or obj.code is None:
822 continue
823 old_obj = default_exceptions.get(obj.code, None)
824 if old_obj is not None and issubclass(obj, old_obj):
825 continue
826 default_exceptions[obj.code] = obj
829_find_exceptions()
830del _find_exceptions
833class Aborter:
834 """When passed a dict of code -> exception items it can be used as
835 callable that raises exceptions. If the first argument to the
836 callable is an integer it will be looked up in the mapping, if it's
837 a WSGI application it will be raised in a proxy exception.
839 The rest of the arguments are forwarded to the exception constructor.
840 """
842 def __init__(
843 self,
844 mapping: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None,
845 extra: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None,
846 ) -> None:
847 if mapping is None:
848 mapping = default_exceptions
849 self.mapping = dict(mapping)
850 if extra is not None:
851 self.mapping.update(extra)
853 def __call__(
854 self, code: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any
855 ) -> "te.NoReturn":
856 from .sansio.response import Response
858 if isinstance(code, Response):
859 raise HTTPException(response=code)
861 if code not in self.mapping:
862 raise LookupError(f"no exception for {code!r}")
864 raise self.mapping[code](*args, **kwargs)
867def abort(
868 status: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any
869) -> "te.NoReturn":
870 """Raises an :py:exc:`HTTPException` for the given status code or WSGI
871 application.
873 If a status code is given, it will be looked up in the list of
874 exceptions and will raise that exception. If passed a WSGI application,
875 it will wrap it in a proxy WSGI exception and raise that::
877 abort(404) # 404 Not Found
878 abort(Response('Hello World'))
880 """
881 _aborter(status, *args, **kwargs)
884_aborter: Aborter = Aborter()