codekingpro/portable-devtools
114k
1"""Implements a number of Python exceptions which can be raised from within2a view to trigger a standard HTTP non-200 response.3 4Usage Example5-------------6 7.. code-block:: python8 9 from werkzeug.wrappers.request import Request10 from werkzeug.exceptions import HTTPException, NotFound11 12 def view(request):13 raise NotFound()14 15 @Request.application16 def application(request):17 try:18 return view(request)19 except HTTPException as e:20 return e21 22As you can see from this example those exceptions are callable WSGI23applications. However, they are not Werkzeug response objects. You24can get a response object by calling ``get_response()`` on a HTTP25exception.26 27Keep in mind that you may have to pass an environ (WSGI) or scope28(ASGI) to ``get_response()`` because some errors fetch additional29information relating to the request.30 31If you want to hook in a different exception page to say, a 404 status32code, you can add a second except for a specific subclass of an error:33 34.. code-block:: python35 36 @Request.application37 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 e44 45"""46 47from __future__ import annotations48 49import typing as t50from datetime import datetime51 52from markupsafe import escape53from markupsafe import Markup54 55from ._internal import _get_environ56 57if t.TYPE_CHECKING:58 from _typeshed.wsgi import StartResponse59 from _typeshed.wsgi import WSGIEnvironment60 61 from .datastructures import WWWAuthenticate62 from .sansio.response import Response63 from .wrappers.request import Request as WSGIRequest64 from .wrappers.response import Response as WSGIResponse65 66 67class HTTPException(Exception):68 """The base class for all HTTP exceptions. This exception can be called as a WSGI69 application to render a default error page or you can catch the subclasses70 of it independently and render nicer error messages.71 72 .. versionchanged:: 2.173 Removed the ``wrap`` class method.74 """75 76 code: int | None = None77 description: str | None = None78 79 def __init__(80 self,81 description: str | None = None,82 response: Response | None = None,83 ) -> None:84 super().__init__()85 if description is not None:86 self.description = description87 self.response = response88 89 @property90 def name(self) -> str:91 """The status name."""92 from .http import HTTP_STATUS_CODES93 94 return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore95 96 def get_description(97 self,98 environ: WSGIEnvironment | None = None,99 scope: dict[str, t.Any] | None = None,100 ) -> str:101 """Get the description."""102 if self.description is None:103 description = ""104 else:105 description = self.description106 107 description = escape(description).replace("\n", Markup("<br>"))108 return f"<p>{description}</p>"109 110 def get_body(111 self,112 environ: WSGIEnvironment | None = None,113 scope: dict[str, t.Any] | None = None,114 ) -> str:115 """Get the HTML body."""116 return (117 "<!doctype html>\n"118 "<html lang=en>\n"119 f"<title>{self.code} {escape(self.name)}</title>\n"120 f"<h1>{escape(self.name)}</h1>\n"121 f"{self.get_description(environ)}\n"122 )123 124 def get_headers(125 self,126 environ: WSGIEnvironment | None = None,127 scope: dict[str, t.Any] | None = None,128 ) -> list[tuple[str, str]]:129 """Get a list of headers."""130 return [("Content-Type", "text/html; charset=utf-8")]131 132 def get_response(133 self,134 environ: WSGIEnvironment | WSGIRequest | None = None,135 scope: dict[str, t.Any] | None = None,136 ) -> Response:137 """Get a response object. If one was passed to the exception138 it's returned directly.139 140 :param environ: the optional environ for the request. This141 can be used to modify the response depending142 on how the request looked like.143 :return: a :class:`Response` object or a subclass thereof.144 """145 from .wrappers.response import Response as WSGIResponse # noqa: F811146 147 if self.response is not None:148 return self.response149 if environ is not None:150 environ = _get_environ(environ)151 headers = self.get_headers(environ, scope)152 return WSGIResponse(self.get_body(environ, scope), self.code, headers)153 154 def __call__(155 self, environ: WSGIEnvironment, start_response: StartResponse156 ) -> t.Iterable[bytes]:157 """Call the exception as WSGI application.158 159 :param environ: the WSGI environment.160 :param start_response: the response callable provided by the WSGI161 server.162 """163 response = t.cast("WSGIResponse", self.get_response(environ))164 return response(environ, start_response)165 166 def __str__(self) -> str:167 code = self.code if self.code is not None else "???"168 return f"{code} {self.name}: {self.description}"169 170 def __repr__(self) -> str:171 code = self.code if self.code is not None else "???"172 return f"<{type(self).__name__} '{code}: {self.name}'>"173 174 175class BadRequest(HTTPException):176 """*400* `Bad Request`177 178 Raise if the browser sends something to the application the application179 or server cannot handle.180 """181 182 code = 400183 description = (184 "The browser (or proxy) sent a request that this server could "185 "not understand."186 )187 188 189class BadRequestKeyError(BadRequest, KeyError):190 """An exception that is used to signal both a :exc:`KeyError` and a191 :exc:`BadRequest`. Used by many of the datastructures.192 """193 194 _description = BadRequest.description195 #: Show the KeyError along with the HTTP error message in the196 #: response. This should be disabled in production, but can be197 #: useful in a debug mode.198 show_exception = False199 200 def __init__(self, arg: str | None = None, *args: t.Any, **kwargs: t.Any):201 super().__init__(*args, **kwargs)202 203 if arg is None:204 KeyError.__init__(self)205 else:206 KeyError.__init__(self, arg)207 208 @property # type: ignore209 def description(self) -> str:210 if self.show_exception:211 return (212 f"{self._description}\n"213 f"{KeyError.__name__}: {KeyError.__str__(self)}"214 )215 216 return self._description217 218 @description.setter219 def description(self, value: str) -> None:220 self._description = value221 222 223class ClientDisconnected(BadRequest):224 """Internal exception that is raised if Werkzeug detects a disconnected225 client. Since the client is already gone at that point attempting to226 send the error message to the client might not work and might ultimately227 result in another exception in the server. Mainly this is here so that228 it is silenced by default as far as Werkzeug is concerned.229 230 Since disconnections cannot be reliably detected and are unspecified231 by WSGI to a large extent this might or might not be raised if a client232 is gone.233 234 .. versionadded:: 0.8235 """236 237 238class SecurityError(BadRequest):239 """Raised if something triggers a security error. This is otherwise240 exactly like a bad request error.241 242 .. versionadded:: 0.9243 """244 245 246class BadHost(BadRequest):247 """Raised if the submitted host is badly formatted.248 249 .. versionadded:: 0.11.2250 """251 252 253class Unauthorized(HTTPException):254 """*401* ``Unauthorized``255 256 Raise if the user is not authorized to access a resource.257 258 The ``www_authenticate`` argument should be used to set the259 ``WWW-Authenticate`` header. This is used for HTTP basic auth and260 other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate`261 to create correctly formatted values. Strictly speaking a 401262 response is invalid if it doesn't provide at least one value for263 this header, although real clients typically don't care.264 265 :param description: Override the default message used for the body266 of the response.267 :param www-authenticate: A single value, or list of values, for the268 WWW-Authenticate header(s).269 270 .. versionchanged:: 2.0271 Serialize multiple ``www_authenticate`` items into multiple272 ``WWW-Authenticate`` headers, rather than joining them273 into a single value, for better interoperability.274 275 .. versionchanged:: 0.15.3276 If the ``www_authenticate`` argument is not set, the277 ``WWW-Authenticate`` header is not set.278 279 .. versionchanged:: 0.15.3280 The ``response`` argument was restored.281 282 .. versionchanged:: 0.15.1283 ``description`` was moved back as the first argument, restoring284 its previous position.285 286 .. versionchanged:: 0.15.0287 ``www_authenticate`` was added as the first argument, ahead of288 ``description``.289 """290 291 code = 401292 description = (293 "The server could not verify that you are authorized to access"294 " the URL requested. You either supplied the wrong credentials"295 " (e.g. a bad password), or your browser doesn't understand"296 " how to supply the credentials required."297 )298 299 def __init__(300 self,301 description: str | None = None,302 response: Response | None = None,303 www_authenticate: None | (WWWAuthenticate | t.Iterable[WWWAuthenticate]) = None,304 ) -> None:305 super().__init__(description, response)306 307 from .datastructures import WWWAuthenticate308 309 if isinstance(www_authenticate, WWWAuthenticate):310 www_authenticate = (www_authenticate,)311 312 self.www_authenticate = www_authenticate313 314 def get_headers(315 self,316 environ: WSGIEnvironment | None = None,317 scope: dict[str, t.Any] | None = None,318 ) -> list[tuple[str, str]]:319 headers = super().get_headers(environ, scope)320 if self.www_authenticate:321 headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate)322 return headers323 324 325class Forbidden(HTTPException):326 """*403* `Forbidden`327 328 Raise if the user doesn't have the permission for the requested resource329 but was authenticated.330 """331 332 code = 403333 description = (334 "You don't have the permission to access the requested"335 " resource. It is either read-protected or not readable by the"336 " server."337 )338 339 340class NotFound(HTTPException):341 """*404* `Not Found`342 343 Raise if a resource does not exist and never existed.344 """345 346 code = 404347 description = (348 "The requested URL was not found on the server. If you entered"349 " the URL manually please check your spelling and try again."350 )351 352 353class MethodNotAllowed(HTTPException):354 """*405* `Method Not Allowed`355 356 Raise if the server used a method the resource does not handle. For357 example `POST` if the resource is view only. Especially useful for REST.358 359 The first argument for this exception should be a list of allowed methods.360 Strictly speaking the response would be invalid if you don't provide valid361 methods in the header which you can do with that list.362 """363 364 code = 405365 description = "The method is not allowed for the requested URL."366 367 def __init__(368 self,369 valid_methods: t.Iterable[str] | None = None,370 description: str | None = None,371 response: Response | None = None,372 ) -> None:373 """Takes an optional list of valid http methods374 starting with werkzeug 0.3 the list will be mandatory."""375 super().__init__(description=description, response=response)376 self.valid_methods = valid_methods377 378 def get_headers(379 self,380 environ: WSGIEnvironment | None = None,381 scope: dict[str, t.Any] | None = None,382 ) -> list[tuple[str, str]]:383 headers = super().get_headers(environ, scope)384 if self.valid_methods:385 headers.append(("Allow", ", ".join(self.valid_methods)))386 return headers387 388 389class NotAcceptable(HTTPException):390 """*406* `Not Acceptable`391 392 Raise if the server can't return any content conforming to the393 `Accept` headers of the client.394 """395 396 code = 406397 description = (398 "The resource identified by the request is only capable of"399 " generating response entities which have content"400 " characteristics not acceptable according to the accept"401 " headers sent in the request."402 )403 404 405class RequestTimeout(HTTPException):406 """*408* `Request Timeout`407 408 Raise to signalize a timeout.409 """410 411 code = 408412 description = (413 "The server closed the network connection because the browser"414 " didn't finish the request within the specified time."415 )416 417 418class Conflict(HTTPException):419 """*409* `Conflict`420 421 Raise to signal that a request cannot be completed because it conflicts422 with the current state on the server.423 424 .. versionadded:: 0.7425 """426 427 code = 409428 description = (429 "A conflict happened while processing the request. The"430 " resource might have been modified while the request was being"431 " processed."432 )433 434 435class Gone(HTTPException):436 """*410* `Gone`437 438 Raise if a resource existed previously and went away without new location.439 """440 441 code = 410442 description = (443 "The requested URL is no longer available on this server and"444 " there is no forwarding address. If you followed a link from a"445 " foreign page, please contact the author of this page."446 )447 448 449class LengthRequired(HTTPException):450 """*411* `Length Required`451 452 Raise if the browser submitted data but no ``Content-Length`` header which453 is required for the kind of processing the server does.454 """455 456 code = 411457 description = (458 "A request with this method requires a valid <code>Content-"459 "Length</code> header."460 )461 462 463class PreconditionFailed(HTTPException):464 """*412* `Precondition Failed`465 466 Status code used in combination with ``If-Match``, ``If-None-Match``, or467 ``If-Unmodified-Since``.468 """469 470 code = 412471 description = (472 "The precondition on the request for the URL failed positive evaluation."473 )474 475 476class RequestEntityTooLarge(HTTPException):477 """*413* `Request Entity Too Large`478 479 The status code one should return if the data submitted exceeded a given480 limit.481 """482 483 code = 413484 description = "The data value transmitted exceeds the capacity limit."485 486 487class RequestURITooLarge(HTTPException):488 """*414* `Request URI Too Large`489 490 Like *413* but for too long URLs.491 """492 493 code = 414494 description = (495 "The length of the requested URL exceeds the capacity limit for"496 " this server. The request cannot be processed."497 )498 499 500class UnsupportedMediaType(HTTPException):501 """*415* `Unsupported Media Type`502 503 The status code returned if the server is unable to handle the media type504 the client transmitted.505 """506 507 code = 415508 description = (509 "The server does not support the media type transmitted in the request."510 )511 512 513class RequestedRangeNotSatisfiable(HTTPException):514 """*416* `Requested Range Not Satisfiable`515 516 The client asked for an invalid part of the file.517 518 .. versionadded:: 0.7519 """520 521 code = 416522 description = "The server cannot provide the requested range."523 524 def __init__(525 self,526 length: int | None = None,527 units: str = "bytes",528 description: str | None = None,529 response: Response | None = None,530 ) -> None:531 """Takes an optional `Content-Range` header value based on ``length``532 parameter.533 """534 super().__init__(description=description, response=response)535 self.length = length536 self.units = units537 538 def get_headers(539 self,540 environ: WSGIEnvironment | None = None,541 scope: dict[str, t.Any] | None = None,542 ) -> list[tuple[str, str]]:543 headers = super().get_headers(environ, scope)544 if self.length is not None:545 headers.append(("Content-Range", f"{self.units} */{self.length}"))546 return headers547 548 549class ExpectationFailed(HTTPException):550 """*417* `Expectation Failed`551 552 The server cannot meet the requirements of the Expect request-header.553 554 .. versionadded:: 0.7555 """556 557 code = 417558 description = "The server could not meet the requirements of the Expect header"559 560 561class ImATeapot(HTTPException):562 """*418* `I'm a teapot`563 564 The server should return this if it is a teapot and someone attempted565 to brew coffee with it.566 567 .. versionadded:: 0.7568 """569 570 code = 418571 description = "This server is a teapot, not a coffee machine"572 573 574class UnprocessableEntity(HTTPException):575 """*422* `Unprocessable Entity`576 577 Used if the request is well formed, but the instructions are otherwise578 incorrect.579 """580 581 code = 422582 description = (583 "The request was well-formed but was unable to be followed due"584 " to semantic errors."585 )586 587 588class Locked(HTTPException):589 """*423* `Locked`590 591 Used if the resource that is being accessed is locked.592 """593 594 code = 423595 description = "The resource that is being accessed is locked."596 597 598class FailedDependency(HTTPException):599 """*424* `Failed Dependency`600 601 Used if the method could not be performed on the resource602 because the requested action depended on another action and that action failed.603 """604 605 code = 424606 description = (607 "The method could not be performed on the resource because the"608 " requested action depended on another action and that action"609 " failed."610 )611 612 613class PreconditionRequired(HTTPException):614 """*428* `Precondition Required`615 616 The server requires this request to be conditional, typically to prevent617 the lost update problem, which is a race condition between two or more618 clients attempting to update a resource through PUT or DELETE. By requiring619 each client to include a conditional header ("If-Match" or "If-Unmodified-620 Since") with the proper value retained from a recent GET request, the621 server ensures that each client has at least seen the previous revision of622 the resource.623 """624 625 code = 428626 description = (627 "This request is required to be conditional; try using"628 ' "If-Match" or "If-Unmodified-Since".'629 )630 631 632class _RetryAfter(HTTPException):633 """Adds an optional ``retry_after`` parameter which will set the634 ``Retry-After`` header. May be an :class:`int` number of seconds or635 a :class:`~datetime.datetime`.636 """637 638 def __init__(639 self,640 description: str | None = None,641 response: Response | None = None,642 retry_after: datetime | int | None = None,643 ) -> None:644 super().__init__(description, response)645 self.retry_after = retry_after646 647 def get_headers(648 self,649 environ: WSGIEnvironment | None = None,650 scope: dict[str, t.Any] | None = None,651 ) -> list[tuple[str, str]]:652 headers = super().get_headers(environ, scope)653 654 if self.retry_after:655 if isinstance(self.retry_after, datetime):656 from .http import http_date657 658 value = http_date(self.retry_after)659 else:660 value = str(self.retry_after)661 662 headers.append(("Retry-After", value))663 664 return headers665 666 667class TooManyRequests(_RetryAfter):668 """*429* `Too Many Requests`669 670 The server is limiting the rate at which this user receives671 responses, and this request exceeds that rate. (The server may use672 any convenient method to identify users and their request rates).673 The server may include a "Retry-After" header to indicate how long674 the user should wait before retrying.675 676 :param retry_after: If given, set the ``Retry-After`` header to this677 value. May be an :class:`int` number of seconds or a678 :class:`~datetime.datetime`.679 680 .. versionchanged:: 1.0681 Added ``retry_after`` parameter.682 """683 684 code = 429685 description = "This user has exceeded an allotted request count. Try again later."686 687 688class RequestHeaderFieldsTooLarge(HTTPException):689 """*431* `Request Header Fields Too Large`690 691 The server refuses to process the request because the header fields are too692 large. One or more individual fields may be too large, or the set of all693 headers is too large.694 """695 696 code = 431697 description = "One or more header fields exceeds the maximum size."698 699 700class UnavailableForLegalReasons(HTTPException):701 """*451* `Unavailable For Legal Reasons`702 703 This status code indicates that the server is denying access to the704 resource as a consequence of a legal demand.705 """706 707 code = 451708 description = "Unavailable for legal reasons."709 710 711class InternalServerError(HTTPException):712 """*500* `Internal Server Error`713 714 Raise if an internal server error occurred. This is a good fallback if an715 unknown error occurred in the dispatcher.716 717 .. versionchanged:: 1.0.0718 Added the :attr:`original_exception` attribute.719 """720 721 code = 500722 description = (723 "The server encountered an internal error and was unable to"724 " complete your request. Either the server is overloaded or"725 " there is an error in the application."726 )727 728 def __init__(729 self,730 description: str | None = None,731 response: Response | None = None,732 original_exception: BaseException | None = None,733 ) -> None:734 #: The original exception that caused this 500 error. Can be735 #: used by frameworks to provide context when handling736 #: unexpected errors.737 self.original_exception = original_exception738 super().__init__(description=description, response=response)739 740 741class NotImplemented(HTTPException):742 """*501* `Not Implemented`743 744 Raise if the application does not support the action requested by the745 browser.746 """747 748 code = 501749 description = "The server does not support the action requested by the browser."750 751 752class BadGateway(HTTPException):753 """*502* `Bad Gateway`754 755 If you do proxying in your application you should return this status code756 if you received an invalid response from the upstream server it accessed757 in attempting to fulfill the request.758 """759 760 code = 502761 description = (762 "The proxy server received an invalid response from an upstream server."763 )764 765 766class ServiceUnavailable(_RetryAfter):767 """*503* `Service Unavailable`768 769 Status code you should return if a service is temporarily770 unavailable.771 772 :param retry_after: If given, set the ``Retry-After`` header to this773 value. May be an :class:`int` number of seconds or a774 :class:`~datetime.datetime`.775 776 .. versionchanged:: 1.0777 Added ``retry_after`` parameter.778 """779 780 code = 503781 description = (782 "The server is temporarily unable to service your request due"783 " to maintenance downtime or capacity problems. Please try"784 " again later."785 )786 787 788class GatewayTimeout(HTTPException):789 """*504* `Gateway Timeout`790 791 Status code you should return if a connection to an upstream server792 times out.793 """794 795 code = 504796 description = "The connection to an upstream server timed out."797 798 799class HTTPVersionNotSupported(HTTPException):800 """*505* `HTTP Version Not Supported`801 802 The server does not support the HTTP protocol version used in the request.803 """804 805 code = 505806 description = (807 "The server does not support the HTTP protocol version used in the request."808 )809 810 811default_exceptions: dict[int, type[HTTPException]] = {}812 813 814def _find_exceptions() -> None:815 for obj in globals().values():816 try:817 is_http_exception = issubclass(obj, HTTPException)818 except TypeError:819 is_http_exception = False820 if not is_http_exception or obj.code is None:821 continue822 old_obj = default_exceptions.get(obj.code, None)823 if old_obj is not None and issubclass(obj, old_obj):824 continue825 default_exceptions[obj.code] = obj826 827 828_find_exceptions()829del _find_exceptions830 831 832class Aborter:833 """When passed a dict of code -> exception items it can be used as834 callable that raises exceptions. If the first argument to the835 callable is an integer it will be looked up in the mapping, if it's836 a WSGI application it will be raised in a proxy exception.837 838 The rest of the arguments are forwarded to the exception constructor.839 """840 841 def __init__(842 self,843 mapping: dict[int, type[HTTPException]] | None = None,844 extra: dict[int, type[HTTPException]] | None = None,845 ) -> None:846 if mapping is None:847 mapping = default_exceptions848 self.mapping = dict(mapping)849 if extra is not None:850 self.mapping.update(extra)851 852 def __call__(853 self, code: int | Response, *args: t.Any, **kwargs: t.Any854 ) -> t.NoReturn:855 from .sansio.response import Response856 857 if isinstance(code, Response):858 raise HTTPException(response=code)859 860 if code not in self.mapping:861 raise LookupError(f"no exception for {code!r}")862 863 raise self.mapping[code](*args, **kwargs)864 865 866def abort(status: int | Response, *args: t.Any, **kwargs: t.Any) -> t.NoReturn:867 """Raises an :py:exc:`HTTPException` for the given status code or WSGI868 application.869 870 If a status code is given, it will be looked up in the list of871 exceptions and will raise that exception. If passed a WSGI application,872 it will wrap it in a proxy WSGI exception and raise that::873 874 abort(404) # 404 Not Found875 abort(Response('Hello World'))876 877 """878 _aborter(status, *args, **kwargs)879 880 881_aborter: Aborter = Aborter()882 