Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/exceptions.py: 67%
238 statements
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +0000
« prev ^ index » next coverage.py v7.2.2, created at 2023-03-26 06:03 +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
48from html import escape
50from ._internal import _get_environ
52if t.TYPE_CHECKING:
53 import typing_extensions as te
54 from _typeshed.wsgi import StartResponse
55 from _typeshed.wsgi import WSGIEnvironment
56 from .datastructures import WWWAuthenticate
57 from .sansio.response import Response
58 from .wrappers.request import Request as WSGIRequest # noqa: F401
59 from .wrappers.response import Response as WSGIResponse # noqa: F401
62class HTTPException(Exception):
63 """The base class for all HTTP exceptions. This exception can be called as a WSGI
64 application to render a default error page or you can catch the subclasses
65 of it independently and render nicer error messages.
67 .. versionchanged:: 2.1
68 Removed the ``wrap`` class method.
69 """
71 code: t.Optional[int] = None
72 description: t.Optional[str] = None
74 def __init__(
75 self,
76 description: t.Optional[str] = None,
77 response: t.Optional["Response"] = None,
78 ) -> None:
79 super().__init__()
80 if description is not None:
81 self.description = description
82 self.response = response
84 @property
85 def name(self) -> str:
86 """The status name."""
87 from .http import HTTP_STATUS_CODES
89 return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore
91 def get_description(
92 self,
93 environ: t.Optional["WSGIEnvironment"] = None,
94 scope: t.Optional[dict] = None,
95 ) -> str:
96 """Get the description."""
97 if self.description is None:
98 description = ""
99 elif not isinstance(self.description, str):
100 description = str(self.description)
101 else:
102 description = self.description
104 description = escape(description).replace("\n", "<br>")
105 return f"<p>{description}</p>"
107 def get_body(
108 self,
109 environ: t.Optional["WSGIEnvironment"] = None,
110 scope: t.Optional[dict] = None,
111 ) -> str:
112 """Get the HTML body."""
113 return (
114 "<!doctype html>\n"
115 "<html lang=en>\n"
116 f"<title>{self.code} {escape(self.name)}</title>\n"
117 f"<h1>{escape(self.name)}</h1>\n"
118 f"{self.get_description(environ)}\n"
119 )
121 def get_headers(
122 self,
123 environ: t.Optional["WSGIEnvironment"] = None,
124 scope: t.Optional[dict] = None,
125 ) -> t.List[t.Tuple[str, str]]:
126 """Get a list of headers."""
127 return [("Content-Type", "text/html; charset=utf-8")]
129 def get_response(
130 self,
131 environ: t.Optional[t.Union["WSGIEnvironment", "WSGIRequest"]] = None,
132 scope: t.Optional[dict] = None,
133 ) -> "Response":
134 """Get a response object. If one was passed to the exception
135 it's returned directly.
137 :param environ: the optional environ for the request. This
138 can be used to modify the response depending
139 on how the request looked like.
140 :return: a :class:`Response` object or a subclass thereof.
141 """
142 from .wrappers.response import Response as WSGIResponse # noqa: F811
144 if self.response is not None:
145 return self.response
146 if environ is not None:
147 environ = _get_environ(environ)
148 headers = self.get_headers(environ, scope)
149 return WSGIResponse(self.get_body(environ, scope), self.code, headers)
151 def __call__(
152 self, environ: "WSGIEnvironment", start_response: "StartResponse"
153 ) -> t.Iterable[bytes]:
154 """Call the exception as WSGI application.
156 :param environ: the WSGI environment.
157 :param start_response: the response callable provided by the WSGI
158 server.
159 """
160 response = t.cast("WSGIResponse", self.get_response(environ))
161 return response(environ, start_response)
163 def __str__(self) -> str:
164 code = self.code if self.code is not None else "???"
165 return f"{code} {self.name}: {self.description}"
167 def __repr__(self) -> str:
168 code = self.code if self.code is not None else "???"
169 return f"<{type(self).__name__} '{code}: {self.name}'>"
172class BadRequest(HTTPException):
173 """*400* `Bad Request`
175 Raise if the browser sends something to the application the application
176 or server cannot handle.
177 """
179 code = 400
180 description = (
181 "The browser (or proxy) sent a request that this server could "
182 "not understand."
183 )
186class BadRequestKeyError(BadRequest, KeyError):
187 """An exception that is used to signal both a :exc:`KeyError` and a
188 :exc:`BadRequest`. Used by many of the datastructures.
189 """
191 _description = BadRequest.description
192 #: Show the KeyError along with the HTTP error message in the
193 #: response. This should be disabled in production, but can be
194 #: useful in a debug mode.
195 show_exception = False
197 def __init__(self, arg: t.Optional[str] = None, *args: t.Any, **kwargs: t.Any):
198 super().__init__(*args, **kwargs)
200 if arg is None:
201 KeyError.__init__(self)
202 else:
203 KeyError.__init__(self, arg)
205 @property # type: ignore
206 def description(self) -> str: # type: ignore
207 if self.show_exception:
208 return (
209 f"{self._description}\n"
210 f"{KeyError.__name__}: {KeyError.__str__(self)}"
211 )
213 return self._description
215 @description.setter
216 def description(self, value: str) -> None:
217 self._description = value
220class ClientDisconnected(BadRequest):
221 """Internal exception that is raised if Werkzeug detects a disconnected
222 client. Since the client is already gone at that point attempting to
223 send the error message to the client might not work and might ultimately
224 result in another exception in the server. Mainly this is here so that
225 it is silenced by default as far as Werkzeug is concerned.
227 Since disconnections cannot be reliably detected and are unspecified
228 by WSGI to a large extent this might or might not be raised if a client
229 is gone.
231 .. versionadded:: 0.8
232 """
235class SecurityError(BadRequest):
236 """Raised if something triggers a security error. This is otherwise
237 exactly like a bad request error.
239 .. versionadded:: 0.9
240 """
243class BadHost(BadRequest):
244 """Raised if the submitted host is badly formatted.
246 .. versionadded:: 0.11.2
247 """
250class Unauthorized(HTTPException):
251 """*401* ``Unauthorized``
253 Raise if the user is not authorized to access a resource.
255 The ``www_authenticate`` argument should be used to set the
256 ``WWW-Authenticate`` header. This is used for HTTP basic auth and
257 other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate`
258 to create correctly formatted values. Strictly speaking a 401
259 response is invalid if it doesn't provide at least one value for
260 this header, although real clients typically don't care.
262 :param description: Override the default message used for the body
263 of the response.
264 :param www-authenticate: A single value, or list of values, for the
265 WWW-Authenticate header(s).
267 .. versionchanged:: 2.0
268 Serialize multiple ``www_authenticate`` items into multiple
269 ``WWW-Authenticate`` headers, rather than joining them
270 into a single value, for better interoperability.
272 .. versionchanged:: 0.15.3
273 If the ``www_authenticate`` argument is not set, the
274 ``WWW-Authenticate`` header is not set.
276 .. versionchanged:: 0.15.3
277 The ``response`` argument was restored.
279 .. versionchanged:: 0.15.1
280 ``description`` was moved back as the first argument, restoring
281 its previous position.
283 .. versionchanged:: 0.15.0
284 ``www_authenticate`` was added as the first argument, ahead of
285 ``description``.
286 """
288 code = 401
289 description = (
290 "The server could not verify that you are authorized to access"
291 " the URL requested. You either supplied the wrong credentials"
292 " (e.g. a bad password), or your browser doesn't understand"
293 " how to supply the credentials required."
294 )
296 def __init__(
297 self,
298 description: t.Optional[str] = None,
299 response: t.Optional["Response"] = None,
300 www_authenticate: t.Optional[
301 t.Union["WWWAuthenticate", t.Iterable["WWWAuthenticate"]]
302 ] = None,
303 ) -> None:
304 super().__init__(description, response)
306 from .datastructures import WWWAuthenticate
308 if isinstance(www_authenticate, WWWAuthenticate):
309 www_authenticate = (www_authenticate,)
311 self.www_authenticate = www_authenticate
313 def get_headers(
314 self,
315 environ: t.Optional["WSGIEnvironment"] = None,
316 scope: t.Optional[dict] = None,
317 ) -> t.List[t.Tuple[str, str]]:
318 headers = super().get_headers(environ, scope)
319 if self.www_authenticate:
320 headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate)
321 return headers
324class Forbidden(HTTPException):
325 """*403* `Forbidden`
327 Raise if the user doesn't have the permission for the requested resource
328 but was authenticated.
329 """
331 code = 403
332 description = (
333 "You don't have the permission to access the requested"
334 " resource. It is either read-protected or not readable by the"
335 " server."
336 )
339class NotFound(HTTPException):
340 """*404* `Not Found`
342 Raise if a resource does not exist and never existed.
343 """
345 code = 404
346 description = (
347 "The requested URL was not found on the server. If you entered"
348 " the URL manually please check your spelling and try again."
349 )
352class MethodNotAllowed(HTTPException):
353 """*405* `Method Not Allowed`
355 Raise if the server used a method the resource does not handle. For
356 example `POST` if the resource is view only. Especially useful for REST.
358 The first argument for this exception should be a list of allowed methods.
359 Strictly speaking the response would be invalid if you don't provide valid
360 methods in the header which you can do with that list.
361 """
363 code = 405
364 description = "The method is not allowed for the requested URL."
366 def __init__(
367 self,
368 valid_methods: t.Optional[t.Iterable[str]] = None,
369 description: t.Optional[str] = None,
370 response: t.Optional["Response"] = None,
371 ) -> None:
372 """Takes an optional list of valid http methods
373 starting with werkzeug 0.3 the list will be mandatory."""
374 super().__init__(description=description, response=response)
375 self.valid_methods = valid_methods
377 def get_headers(
378 self,
379 environ: t.Optional["WSGIEnvironment"] = None,
380 scope: t.Optional[dict] = None,
381 ) -> t.List[t.Tuple[str, str]]:
382 headers = super().get_headers(environ, scope)
383 if self.valid_methods:
384 headers.append(("Allow", ", ".join(self.valid_methods)))
385 return headers
388class NotAcceptable(HTTPException):
389 """*406* `Not Acceptable`
391 Raise if the server can't return any content conforming to the
392 `Accept` headers of the client.
393 """
395 code = 406
396 description = (
397 "The resource identified by the request is only capable of"
398 " generating response entities which have content"
399 " characteristics not acceptable according to the accept"
400 " headers sent in the request."
401 )
404class RequestTimeout(HTTPException):
405 """*408* `Request Timeout`
407 Raise to signalize a timeout.
408 """
410 code = 408
411 description = (
412 "The server closed the network connection because the browser"
413 " didn't finish the request within the specified time."
414 )
417class Conflict(HTTPException):
418 """*409* `Conflict`
420 Raise to signal that a request cannot be completed because it conflicts
421 with the current state on the server.
423 .. versionadded:: 0.7
424 """
426 code = 409
427 description = (
428 "A conflict happened while processing the request. The"
429 " resource might have been modified while the request was being"
430 " processed."
431 )
434class Gone(HTTPException):
435 """*410* `Gone`
437 Raise if a resource existed previously and went away without new location.
438 """
440 code = 410
441 description = (
442 "The requested URL is no longer available on this server and"
443 " there is no forwarding address. If you followed a link from a"
444 " foreign page, please contact the author of this page."
445 )
448class LengthRequired(HTTPException):
449 """*411* `Length Required`
451 Raise if the browser submitted data but no ``Content-Length`` header which
452 is required for the kind of processing the server does.
453 """
455 code = 411
456 description = (
457 "A request with this method requires a valid <code>Content-"
458 "Length</code> header."
459 )
462class PreconditionFailed(HTTPException):
463 """*412* `Precondition Failed`
465 Status code used in combination with ``If-Match``, ``If-None-Match``, or
466 ``If-Unmodified-Since``.
467 """
469 code = 412
470 description = (
471 "The precondition on the request for the URL failed positive evaluation."
472 )
475class RequestEntityTooLarge(HTTPException):
476 """*413* `Request Entity Too Large`
478 The status code one should return if the data submitted exceeded a given
479 limit.
480 """
482 code = 413
483 description = "The data value transmitted exceeds the capacity limit."
486class RequestURITooLarge(HTTPException):
487 """*414* `Request URI Too Large`
489 Like *413* but for too long URLs.
490 """
492 code = 414
493 description = (
494 "The length of the requested URL exceeds the capacity limit for"
495 " this server. The request cannot be processed."
496 )
499class UnsupportedMediaType(HTTPException):
500 """*415* `Unsupported Media Type`
502 The status code returned if the server is unable to handle the media type
503 the client transmitted.
504 """
506 code = 415
507 description = (
508 "The server does not support the media type transmitted in the request."
509 )
512class RequestedRangeNotSatisfiable(HTTPException):
513 """*416* `Requested Range Not Satisfiable`
515 The client asked for an invalid part of the file.
517 .. versionadded:: 0.7
518 """
520 code = 416
521 description = "The server cannot provide the requested range."
523 def __init__(
524 self,
525 length: t.Optional[int] = None,
526 units: str = "bytes",
527 description: t.Optional[str] = None,
528 response: t.Optional["Response"] = None,
529 ) -> None:
530 """Takes an optional `Content-Range` header value based on ``length``
531 parameter.
532 """
533 super().__init__(description=description, response=response)
534 self.length = length
535 self.units = units
537 def get_headers(
538 self,
539 environ: t.Optional["WSGIEnvironment"] = None,
540 scope: t.Optional[dict] = None,
541 ) -> t.List[t.Tuple[str, str]]:
542 headers = super().get_headers(environ, scope)
543 if self.length is not None:
544 headers.append(("Content-Range", f"{self.units} */{self.length}"))
545 return headers
548class ExpectationFailed(HTTPException):
549 """*417* `Expectation Failed`
551 The server cannot meet the requirements of the Expect request-header.
553 .. versionadded:: 0.7
554 """
556 code = 417
557 description = "The server could not meet the requirements of the Expect header"
560class ImATeapot(HTTPException):
561 """*418* `I'm a teapot`
563 The server should return this if it is a teapot and someone attempted
564 to brew coffee with it.
566 .. versionadded:: 0.7
567 """
569 code = 418
570 description = "This server is a teapot, not a coffee machine"
573class UnprocessableEntity(HTTPException):
574 """*422* `Unprocessable Entity`
576 Used if the request is well formed, but the instructions are otherwise
577 incorrect.
578 """
580 code = 422
581 description = (
582 "The request was well-formed but was unable to be followed due"
583 " to semantic errors."
584 )
587class Locked(HTTPException):
588 """*423* `Locked`
590 Used if the resource that is being accessed is locked.
591 """
593 code = 423
594 description = "The resource that is being accessed is locked."
597class FailedDependency(HTTPException):
598 """*424* `Failed Dependency`
600 Used if the method could not be performed on the resource
601 because the requested action depended on another action and that action failed.
602 """
604 code = 424
605 description = (
606 "The method could not be performed on the resource because the"
607 " requested action depended on another action and that action"
608 " failed."
609 )
612class PreconditionRequired(HTTPException):
613 """*428* `Precondition Required`
615 The server requires this request to be conditional, typically to prevent
616 the lost update problem, which is a race condition between two or more
617 clients attempting to update a resource through PUT or DELETE. By requiring
618 each client to include a conditional header ("If-Match" or "If-Unmodified-
619 Since") with the proper value retained from a recent GET request, the
620 server ensures that each client has at least seen the previous revision of
621 the resource.
622 """
624 code = 428
625 description = (
626 "This request is required to be conditional; try using"
627 ' "If-Match" or "If-Unmodified-Since".'
628 )
631class _RetryAfter(HTTPException):
632 """Adds an optional ``retry_after`` parameter which will set the
633 ``Retry-After`` header. May be an :class:`int` number of seconds or
634 a :class:`~datetime.datetime`.
635 """
637 def __init__(
638 self,
639 description: t.Optional[str] = None,
640 response: t.Optional["Response"] = None,
641 retry_after: t.Optional[t.Union[datetime, int]] = None,
642 ) -> None:
643 super().__init__(description, response)
644 self.retry_after = retry_after
646 def get_headers(
647 self,
648 environ: t.Optional["WSGIEnvironment"] = None,
649 scope: t.Optional[dict] = None,
650 ) -> t.List[t.Tuple[str, str]]:
651 headers = super().get_headers(environ, scope)
653 if self.retry_after:
654 if isinstance(self.retry_after, datetime):
655 from .http import http_date
657 value = http_date(self.retry_after)
658 else:
659 value = str(self.retry_after)
661 headers.append(("Retry-After", value))
663 return headers
666class TooManyRequests(_RetryAfter):
667 """*429* `Too Many Requests`
669 The server is limiting the rate at which this user receives
670 responses, and this request exceeds that rate. (The server may use
671 any convenient method to identify users and their request rates).
672 The server may include a "Retry-After" header to indicate how long
673 the user should wait before retrying.
675 :param retry_after: If given, set the ``Retry-After`` header to this
676 value. May be an :class:`int` number of seconds or a
677 :class:`~datetime.datetime`.
679 .. versionchanged:: 1.0
680 Added ``retry_after`` parameter.
681 """
683 code = 429
684 description = "This user has exceeded an allotted request count. Try again later."
687class RequestHeaderFieldsTooLarge(HTTPException):
688 """*431* `Request Header Fields Too Large`
690 The server refuses to process the request because the header fields are too
691 large. One or more individual fields may be too large, or the set of all
692 headers is too large.
693 """
695 code = 431
696 description = "One or more header fields exceeds the maximum size."
699class UnavailableForLegalReasons(HTTPException):
700 """*451* `Unavailable For Legal Reasons`
702 This status code indicates that the server is denying access to the
703 resource as a consequence of a legal demand.
704 """
706 code = 451
707 description = "Unavailable for legal reasons."
710class InternalServerError(HTTPException):
711 """*500* `Internal Server Error`
713 Raise if an internal server error occurred. This is a good fallback if an
714 unknown error occurred in the dispatcher.
716 .. versionchanged:: 1.0.0
717 Added the :attr:`original_exception` attribute.
718 """
720 code = 500
721 description = (
722 "The server encountered an internal error and was unable to"
723 " complete your request. Either the server is overloaded or"
724 " there is an error in the application."
725 )
727 def __init__(
728 self,
729 description: t.Optional[str] = None,
730 response: t.Optional["Response"] = None,
731 original_exception: t.Optional[BaseException] = None,
732 ) -> None:
733 #: The original exception that caused this 500 error. Can be
734 #: used by frameworks to provide context when handling
735 #: unexpected errors.
736 self.original_exception = original_exception
737 super().__init__(description=description, response=response)
740class NotImplemented(HTTPException):
741 """*501* `Not Implemented`
743 Raise if the application does not support the action requested by the
744 browser.
745 """
747 code = 501
748 description = "The server does not support the action requested by the browser."
751class BadGateway(HTTPException):
752 """*502* `Bad Gateway`
754 If you do proxying in your application you should return this status code
755 if you received an invalid response from the upstream server it accessed
756 in attempting to fulfill the request.
757 """
759 code = 502
760 description = (
761 "The proxy server received an invalid response from an upstream server."
762 )
765class ServiceUnavailable(_RetryAfter):
766 """*503* `Service Unavailable`
768 Status code you should return if a service is temporarily
769 unavailable.
771 :param retry_after: If given, set the ``Retry-After`` header to this
772 value. May be an :class:`int` number of seconds or a
773 :class:`~datetime.datetime`.
775 .. versionchanged:: 1.0
776 Added ``retry_after`` parameter.
777 """
779 code = 503
780 description = (
781 "The server is temporarily unable to service your request due"
782 " to maintenance downtime or capacity problems. Please try"
783 " again later."
784 )
787class GatewayTimeout(HTTPException):
788 """*504* `Gateway Timeout`
790 Status code you should return if a connection to an upstream server
791 times out.
792 """
794 code = 504
795 description = "The connection to an upstream server timed out."
798class HTTPVersionNotSupported(HTTPException):
799 """*505* `HTTP Version Not Supported`
801 The server does not support the HTTP protocol version used in the request.
802 """
804 code = 505
805 description = (
806 "The server does not support the HTTP protocol version used in the request."
807 )
810default_exceptions: t.Dict[int, t.Type[HTTPException]] = {}
813def _find_exceptions() -> None:
814 for obj in globals().values():
815 try:
816 is_http_exception = issubclass(obj, HTTPException)
817 except TypeError:
818 is_http_exception = False
819 if not is_http_exception or obj.code is None:
820 continue
821 old_obj = default_exceptions.get(obj.code, None)
822 if old_obj is not None and issubclass(obj, old_obj):
823 continue
824 default_exceptions[obj.code] = obj
827_find_exceptions()
828del _find_exceptions
831class Aborter:
832 """When passed a dict of code -> exception items it can be used as
833 callable that raises exceptions. If the first argument to the
834 callable is an integer it will be looked up in the mapping, if it's
835 a WSGI application it will be raised in a proxy exception.
837 The rest of the arguments are forwarded to the exception constructor.
838 """
840 def __init__(
841 self,
842 mapping: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None,
843 extra: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None,
844 ) -> None:
845 if mapping is None:
846 mapping = default_exceptions
847 self.mapping = dict(mapping)
848 if extra is not None:
849 self.mapping.update(extra)
851 def __call__(
852 self, code: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any
853 ) -> "te.NoReturn":
854 from .sansio.response import Response
856 if isinstance(code, Response):
857 raise HTTPException(response=code)
859 if code not in self.mapping:
860 raise LookupError(f"no exception for {code!r}")
862 raise self.mapping[code](*args, **kwargs)
865def abort(
866 status: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any
867) -> "te.NoReturn":
868 """Raises an :py:exc:`HTTPException` for the given status code or WSGI
869 application.
871 If a status code is given, it will be looked up in the list of
872 exceptions and will raise that exception. If passed a WSGI application,
873 it will wrap it in a proxy WSGI exception and raise that::
875 abort(404) # 404 Not Found
876 abort(Response('Hello World'))
878 """
879 _aborter(status, *args, **kwargs)
882_aborter: Aborter = Aborter()