Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/exceptions.py: 68%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import typing as t
4import wsgiref.types as wt
5from datetime import datetime
6from http import HTTPStatus
8from markupsafe import escape
9from markupsafe import Markup
11from ._internal import _get_environ
13if t.TYPE_CHECKING:
14 from .datastructures.auth import WWWAuthenticate
15 from .wrappers.response import Response as WSGIResponse
18class HTTPException(Exception):
19 """An HTTP error status as an exception. It can be converted to an HTML
20 response describing the error, and called as a WSGI application.
22 All the exceptions take the following two keyword parameters to customize
23 the error. Certain exceptions may take other arguments based on the headers
24 they need to return.
26 :param description: Override the default error page description.
27 :param response: Use this response instead of generating one. This allows
28 using the exception for flow control, while completely controlling the
29 response.
31 .. versionchanged:: 2.1
32 The ``wrap`` class method was removed.
33 """
35 code: int = 0
36 name: str = ""
37 description: str = ""
39 _override_get_description = False
40 _override_get_body = False
41 _override_get_headers = False
42 _override_get_response = False
44 def __init_subclass__(cls, **kwargs: t.Any) -> None:
45 super().__init_subclass__(**kwargs)
47 # Automatically fill in `name` from the `HTTPStatus` enum.
48 if not cls.name:
49 try:
50 cls.name = HTTPStatus(cls.code).phrase
51 except ValueError:
52 pass
54 # Automatically add the standard code, title, link to the top of the doc.
55 # Skip special subclasses that inherit `code`.
56 if "code" in cls.__dict__:
57 title = f"{cls.code} {cls.name}"
58 doc = (
59 f"{title}\n\n{cls.description}\n\nhttps://developer.mozilla.org"
60 f"/en-US/docs/Web/HTTP/Reference/Status/{cls.code}"
61 )
63 if not cls.__doc__:
64 cls.__doc__ = doc
65 elif not cls.__doc__.lstrip().startswith(title):
66 cls.__doc__ = f"{doc}\n\n{cls.__doc__.lstrip()}"
68 for name in ("get_description", "get_body", "get_headers", "get_response"):
69 if getattr(cls, name) is not getattr(HTTPException, name):
70 if name != "get_response":
71 import warnings
73 warnings.warn(
74 f"The '{name}' method is deprecated and will be removed in"
75 " Werkzeug 4.0. Override 'get_response' instead.",
76 DeprecationWarning,
77 stacklevel=2,
78 )
80 setattr(cls, f"_override_{name}", True)
82 def __init__(
83 self, description: str | None = None, response: WSGIResponse | None = None
84 ) -> None:
85 super().__init__()
87 if description is not None:
88 self.description = description
90 self.response = response
92 def get_description(
93 self,
94 environ: wt.WSGIEnvironment | None = None,
95 scope: dict[str, t.Any] | None = None,
96 ) -> str:
97 """The description as an HTML paragraph. HTML characters are escaped,
98 and newlines are replaced with ``<br>``.
100 .. deprecated:: 3.2
101 Will be removed in Werkzeug 4.0. Use ``get_response`` instead.
102 """
103 import warnings
105 warnings.warn(
106 "The 'get_description' method is deprecated and will be removed in"
107 " Werkzeug 4.0. Use 'get_response' instead.",
108 DeprecationWarning,
109 stacklevel=2,
110 )
111 return self._get_description()
113 def _get_description(self) -> str:
114 # TODO remove after deprecation
115 description = escape(self.description).replace("\n", Markup("<br>"))
116 return f"<p>{description}</p>"
118 def get_body(self, environ: None = None, scope: None = None) -> str:
119 """The HTML error page.
121 .. deprecated:: 3.2
122 Will be removed in Werkzeug 4.0. Use ``get_response`` instead.
123 """
124 import warnings
126 warnings.warn(
127 "The 'get_body' method is deprecated and will be removed in"
128 " Werkzeug 4.0. Use 'get_response' instead.",
129 DeprecationWarning,
130 stacklevel=2,
131 )
132 return self._get_body()
134 def _get_body(self) -> str:
135 # TODO remove after deprecation
136 if self._override_get_description:
137 description = self.get_description()
138 else:
139 description = self._get_description()
141 title = f"{self.code} {escape(self.name)}"
142 return (
143 "<!doctype html>\n<html lang=en>\n<head>\n<meta charset=utf-8>\n"
144 f"<title>{title}</title>\n</head>\n<body>\n<h1>{title}</h1>\n"
145 f"{description}\n</body>\n</html>\n"
146 )
148 def get_headers(
149 self, environ: None = None, scope: None = None
150 ) -> list[tuple[str, str]]:
151 """The headers to send with the response.
153 .. deprecated:: 3.2
154 Will be removed in Werkzeug 4.0. Use ``get_response`` instead.
155 """
156 import warnings
158 warnings.warn(
159 "The 'get_headers' method is deprecated and will be removed in"
160 " Werkzeug 4.0. Use 'get_response' instead.",
161 DeprecationWarning,
162 stacklevel=2,
163 )
164 return [("Content-Type", "text/html; charset=utf-8")]
166 def get_response(self, environ: None = None, scope: None = None) -> WSGIResponse:
167 """Get the response for this error. If :attr:`response` was given when
168 creating the error, it will be returned. Otherwise, a default response
169 with an HTML error page and any error-specific headers is built.
171 .. versionchanged:: 3.2
172 The ``environ`` and ``scope`` parameters are deprecated and will be
173 removed in Werkzeug 4.0.
174 """
175 from .wrappers.response import Response
177 if self.response is not None:
178 return self.response
180 if environ is not None:
181 import warnings
183 warnings.warn(
184 "The 'environ' parameter is deprecated and will be"
185 " removed in Werkzeug 4.0.",
186 DeprecationWarning,
187 stacklevel=2,
188 )
189 environ = _get_environ(environ)
191 if scope is not None:
192 import warnings
194 warnings.warn(
195 "The 'scope' parameter is deprecated and will be"
196 " removed in Werkzeug 4.0.",
197 DeprecationWarning,
198 stacklevel=2,
199 )
201 if self._override_get_headers:
202 content_type = None
203 headers = self.get_headers(environ, scope)
204 else:
205 content_type = "text/html; charset=utf-8"
206 headers = None
208 if self._override_get_body:
209 body = self.get_body(environ, scope)
210 else:
211 body = self._get_body()
213 return Response(body, self.code, headers, content_type=content_type)
215 def __call__(
216 self, environ: wt.WSGIEnvironment, start_response: wt.StartResponse
217 ) -> t.Iterable[bytes]:
218 if self._override_get_response:
219 response = self.get_response(environ) # type: ignore[arg-type]
220 else:
221 response = self.get_response()
223 return response(environ, start_response)
225 def __str__(self) -> str:
226 return f"{self.code} {self.name}: {self.description}"
228 def __repr__(self) -> str:
229 return f"<{type(self).__name__} '{self.code} {self.name}'>"
232class BadRequest(HTTPException):
233 code = 400
234 description = "The request is invalid."
237class BadRequestKeyError(BadRequest, KeyError):
238 """Signals both a :exc:`.KeyError` and a :exc:`.BadRequest`. Raised for a
239 missing key in :class:`.MultiDict` and :class:`.Headers`. If a client didn't
240 send an expected query arg, form field, cookie, header, etc., a 400 error
241 response is correct. Or it can be caught as a normal ``KeyError`` in code.
242 """
244 _description = BadRequest.description
245 #: Show the KeyError along with the HTTP error message in the
246 #: response. This should be disabled in production, but can be
247 #: useful in a debug mode.
248 show_exception = False
250 def __init__(self, arg: object | None = None, *args: t.Any, **kwargs: t.Any):
251 super().__init__(*args, **kwargs)
253 if arg is None:
254 KeyError.__init__(self)
255 else:
256 KeyError.__init__(self, arg)
258 @property
259 def description(self) -> str:
260 if self.show_exception:
261 return f"{self._description}\n{KeyError.__name__}: {KeyError.__str__(self)}"
263 return self._description
265 @description.setter
266 def description(self, value: str) -> None:
267 self._description = value
270class ClientDisconnected(BadRequest):
271 """The client disconnected while processing the request. Since the
272 connection is closed, this probably can't be returned as a response.
274 .. versionadded:: 0.8
275 """
278class SecurityError(BadRequest):
279 """The request is invalid in some way that may indicate a security event.
281 .. versionadded:: 0.9
282 """
285class BadHost(BadRequest):
286 """The request's ``Host`` header is invalid.
288 .. versionadded:: 0.11.2
289 """
292class Unauthorized(HTTPException):
293 """
294 :param www_authenticate: Set one or more ``WWW-Authenticate`` headers to
295 tell the client which authentication methods are accepted. The response
296 is technically invalid if this is not given.
298 .. versionchanged:: 2.0
299 Serialize multiple ``www_authenticate`` items into multiple
300 ``WWW-Authenticate`` headers, rather than joining them
301 into a single value, for better interoperability.
303 .. versionchanged:: 0.15.3
304 If the ``www_authenticate`` argument is not set, the
305 ``WWW-Authenticate`` header is not set.
307 The ``response`` argument was restored.
309 .. versionchanged:: 0.15.1
310 ``description`` was moved back as the first argument, restoring
311 its previous position.
313 .. versionchanged:: 0.15.0
314 ``www_authenticate`` was added as the first argument, ahead of
315 ``description``.
316 """
318 code = 401
319 description = "Missing or invalid authentication credentials."
321 def __init__(
322 self,
323 description: str | None = None,
324 response: WSGIResponse | None = None,
325 www_authenticate: WWWAuthenticate | t.Iterable[WWWAuthenticate] | None = None,
326 ) -> None:
327 super().__init__(description=description, response=response)
329 from .datastructures.auth import WWWAuthenticate
331 if isinstance(www_authenticate, WWWAuthenticate):
332 www_authenticate = (www_authenticate,)
334 self.www_authenticate = www_authenticate
336 def get_response(self, environ: None = None, scope: None = None) -> WSGIResponse:
337 response = super().get_response()
338 response.www_authenticate = self.www_authenticate
339 return response
342class Forbidden(HTTPException):
343 code = 403
344 description = "Insufficient permissions for the requested resource."
347class NotFound(HTTPException):
348 code = 404
349 description = "The requested resource does not exist."
352class MethodNotAllowed(HTTPException):
353 """
354 :param valid_methods: Set the ``Allow`` header to inform the client which
355 methods are allowed. The response is technically invalid if this is not
356 given.
357 """
359 code = 405
360 description = "The request method is not allowed."
362 def __init__(
363 self,
364 valid_methods: t.Iterable[str] | None = None,
365 description: str | None = None,
366 response: WSGIResponse | None = None,
367 ) -> None:
368 super().__init__(description=description, response=response)
370 if valid_methods is None:
371 valid_methods = ()
373 self.valid_methods: t.Iterable[str] = valid_methods
375 def get_response(self, environ: None = None, scope: None = None) -> WSGIResponse:
376 response = super().get_response()
377 response.allow = self.valid_methods
378 return response
381class NotAcceptable(HTTPException):
382 code = 406
383 description = "The client's indicated preferences could not be matched."
386class RequestTimeout(HTTPException):
387 code = 408
388 description = "A complete request was not received in time."
391class Conflict(HTTPException):
392 """
393 .. versionadded:: 0.7
394 """
396 code = 409
397 description = "There is a conflict with the current state of the resource."
400class Gone(HTTPException):
401 code = 410
402 description = "The requested resource no longer exists."
405class LengthRequired(HTTPException):
406 code = 411
407 description = "The request must specify its length."
410class PreconditionFailed(HTTPException):
411 code = 412
412 description = (
413 "The requested conditions do not match the current state of the resource."
414 )
417class RequestEntityTooLarge(HTTPException):
418 code = 413
419 description = "The data is larger than what the server allows."
422class RequestURITooLarge(HTTPException):
423 code = 414
424 description = "The URL is longer than what the server allows."
427class UnsupportedMediaType(HTTPException):
428 code = 415
429 description = "The data format is not supported."
432class RequestedRangeNotSatisfiable(HTTPException):
433 """
434 .. versionadded:: 0.7
435 """
437 code = 416
438 description = "The requested range cannot be provided."
440 def __init__(
441 self,
442 length: int | None = None,
443 units: str = "bytes",
444 description: str | None = None,
445 response: WSGIResponse | None = None,
446 ) -> None:
447 from .datastructures.range import ContentRange
449 super().__init__(description=description, response=response)
450 self.content_range = ContentRange(units=units, length=length)
452 def get_response(self, environ: None = None, scope: None = None) -> WSGIResponse:
453 response = super().get_response()
454 response.content_range = self.content_range
455 return response
458class ExpectationFailed(HTTPException):
459 """
460 .. versionadded:: 0.7
461 """
463 code = 417
464 description = "The requested expectations could not be met."
467class ImATeapot(HTTPException):
468 """
469 .. versionadded:: 0.7
470 """
472 code = 418
473 description = "The server is a teapot, not a coffee machine."
476class MisdirectedRequest(HTTPException):
477 """
478 .. versionadded:: 3.1
479 """
481 code = 421
482 description = (
483 "The server is not able to produce a response, the request should be"
484 " directed to another server."
485 )
488class UnprocessableEntity(HTTPException):
489 code = 422
490 description = "The data format is supported, but the data is invalid."
493class Locked(HTTPException):
494 code = 423
495 description = "The resource is currently locked and can't be accessed."
498class FailedDependency(HTTPException):
499 code = 424
500 description = "The current action depends on another action that failed."
503class PreconditionRequired(HTTPException):
504 code = 428
505 description = "The request must contain conditions."
508class _RetryAfter(HTTPException):
509 """Adds an optional ``retry_after`` parameter which will set the
510 ``Retry-After`` header.
511 """
513 def __init__(
514 self,
515 description: str | None = None,
516 response: WSGIResponse | None = None,
517 retry_after: datetime | int | None = None,
518 ) -> None:
519 super().__init__(description, response)
520 self.retry_after = retry_after
522 def get_response(self, environ: None = None, scope: None = None) -> WSGIResponse:
523 response = super().get_response()
524 response.retry_after = self.retry_after
525 return response
528class TooManyRequests(_RetryAfter):
529 """
530 :param retry_after: Set the ``Retry-After`` header. An ``int`` number of
531 seconds from now, or a specific ``datetime``.
533 .. versionchanged:: 1.0
534 The ``retry_after`` parameter was added.
535 """
537 code = 429
538 description = "This client has made too many requests too quickly."
541class RequestHeaderFieldsTooLarge(HTTPException):
542 code = 431
543 description = "The headers are larger than what the server allows."
546class UnavailableForLegalReasons(HTTPException):
547 code = 451
548 description = "The resource is not available for legal reasons."
551class InternalServerError(HTTPException):
552 """
553 :param original_exception: The exception that caused this 500 error.
555 .. versionchanged:: 1.0.0
556 The ``original_exception`` parameter and attribute was added.
557 """
559 code = 500
560 description = "The server encountered an unexpected error."
562 def __init__(
563 self,
564 description: str | None = None,
565 response: WSGIResponse | None = None,
566 original_exception: BaseException | None = None,
567 ) -> None:
568 super().__init__(description=description, response=response)
570 self.original_exception = original_exception
571 """The exception that caused this 500 error. Can be used to provide
572 context when handling unexpected errors.
573 """
576class NotImplemented(HTTPException):
577 code = 501
578 description = "The server does not support the requested action."
581class BadGateway(HTTPException):
582 code = 502
583 description = "The proxy received an invalid response from the upstream server."
586class ServiceUnavailable(_RetryAfter):
587 """
588 :param retry_after: Set the ``Retry-After`` header. An ``int`` number of
589 seconds from now, or a specific ``datetime``.
591 .. versionchanged:: 1.0
592 The ``retry_after`` parameter was added.
593 """
595 code = 503
596 description = "The server is not currently available to handle the request."
599class GatewayTimeout(HTTPException):
600 code = 504
601 description = (
602 "The proxy did not receive a complete response from the upstream server"
603 " in time."
604 )
607class HTTPVersionNotSupported(HTTPException):
608 code = 505
609 description = "The server does not support the HTTP version used by the client."
612_default_exceptions: dict[int, type[HTTPException]] = {}
615def _find_exceptions() -> None:
616 for obj in globals().values():
617 if obj is HTTPException:
618 continue
620 try:
621 is_http_exception = issubclass(obj, HTTPException)
622 except TypeError:
623 is_http_exception = False
625 if not is_http_exception or not obj.code:
626 continue
628 if (old_obj := _default_exceptions.get(obj.code, None)) is not None:
629 if issubclass(obj, old_obj):
630 # special subclass, ignore
631 continue
633 # accidentally redefined a status code
634 raise TypeError(f"{obj} redefines {obj.code} from {old_obj}")
636 _default_exceptions[obj.code] = obj
639_find_exceptions()
640del _find_exceptions
643class Aborter:
644 """Map status codes to HTTP exceptions. Used by :func:`abort`.
646 :param mapping: The map of HTTP status codes to HTTP exceptions. Defaults to
647 Werkzeug's built-in exceptions.
648 :param extra: Extend ``mapping`` with these additional items. Use to
649 override or extend the default exceptions.
650 """
652 def __init__(
653 self,
654 mapping: dict[int, type[HTTPException]] | None = None,
655 extra: dict[int, type[HTTPException]] | None = None,
656 ) -> None:
657 if mapping is None:
658 mapping = _default_exceptions
660 self.mapping = dict(mapping)
662 if extra is not None:
663 self.mapping.update(extra)
665 def __call__(
666 self, status: int | WSGIResponse, *args: t.Any, **kwargs: t.Any
667 ) -> t.NoReturn:
668 """Raise an :exc:`.HTTPException` for the given status or response.
670 :param status: An HTTP error status code, which will raise the matching
671 ``HTTPException`` class. Or a response, which will raise a generic
672 ``HTTPException`` with it as the response.
673 :param args: Positional arguments passed when creating the status exception.
674 :param kwargs: Keyword arguments passed when creating the status exception.
675 """
676 from .sansio.response import Response
678 if isinstance(status, Response):
679 raise HTTPException(response=status)
681 if status not in self.mapping:
682 raise LookupError(f"no exception for {status!r}")
684 raise self.mapping[status](*args, **kwargs)
687_aborter: Aborter = Aborter()
690def abort(status: int | WSGIResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:
691 """Raise an :exc:`.HTTPException` for the given status or response.
693 :param status: An HTTP error status code, which will raise the matching
694 ``HTTPException`` class. Or a response, which will raise a generic
695 ``HTTPException`` with it as the response.
696 :param args: Positional arguments passed when creating the status exception.
697 :param kwargs: Keyword arguments passed when creating the status exception.
698 """
699 _aborter(status, *args, **kwargs)