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

280 statements  

1from __future__ import annotations 

2 

3import typing as t 

4import wsgiref.types as wt 

5from datetime import datetime 

6from http import HTTPStatus 

7 

8from markupsafe import escape 

9from markupsafe import Markup 

10 

11from ._internal import _get_environ 

12 

13if t.TYPE_CHECKING: 

14 from .datastructures.auth import WWWAuthenticate 

15 from .wrappers.response import Response as WSGIResponse 

16 

17 

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. 

21 

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. 

25 

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. 

30 

31 .. versionchanged:: 2.1 

32 The ``wrap`` class method was removed. 

33 """ 

34 

35 code: int = 0 

36 name: str = "" 

37 description: str = "" 

38 

39 _override_get_description = False 

40 _override_get_body = False 

41 _override_get_headers = False 

42 _override_get_response = False 

43 

44 def __init_subclass__(cls, **kwargs: t.Any) -> None: 

45 super().__init_subclass__(**kwargs) 

46 

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 

53 

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 ) 

62 

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()}" 

67 

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 

72 

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 ) 

79 

80 setattr(cls, f"_override_{name}", True) 

81 

82 def __init__( 

83 self, description: str | None = None, response: WSGIResponse | None = None 

84 ) -> None: 

85 super().__init__() 

86 

87 if description is not None: 

88 self.description = description 

89 

90 self.response = response 

91 

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>``. 

99 

100 .. deprecated:: 3.2 

101 Will be removed in Werkzeug 4.0. Use ``get_response`` instead. 

102 """ 

103 import warnings 

104 

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() 

112 

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>" 

117 

118 def get_body(self, environ: None = None, scope: None = None) -> str: 

119 """The HTML error page. 

120 

121 .. deprecated:: 3.2 

122 Will be removed in Werkzeug 4.0. Use ``get_response`` instead. 

123 """ 

124 import warnings 

125 

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() 

133 

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() 

140 

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 ) 

147 

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. 

152 

153 .. deprecated:: 3.2 

154 Will be removed in Werkzeug 4.0. Use ``get_response`` instead. 

155 """ 

156 import warnings 

157 

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")] 

165 

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. 

170 

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 

176 

177 if self.response is not None: 

178 return self.response 

179 

180 if environ is not None: 

181 import warnings 

182 

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) 

190 

191 if scope is not None: 

192 import warnings 

193 

194 warnings.warn( 

195 "The 'scope' parameter is deprecated and will be" 

196 " removed in Werkzeug 4.0.", 

197 DeprecationWarning, 

198 stacklevel=2, 

199 ) 

200 

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 

207 

208 if self._override_get_body: 

209 body = self.get_body(environ, scope) 

210 else: 

211 body = self._get_body() 

212 

213 return Response(body, self.code, headers, content_type=content_type) 

214 

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() 

222 

223 return response(environ, start_response) 

224 

225 def __str__(self) -> str: 

226 return f"{self.code} {self.name}: {self.description}" 

227 

228 def __repr__(self) -> str: 

229 return f"<{type(self).__name__} '{self.code} {self.name}'>" 

230 

231 

232class BadRequest(HTTPException): 

233 code = 400 

234 description = "The request is invalid." 

235 

236 

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 """ 

243 

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 

249 

250 def __init__(self, arg: object | None = None, *args: t.Any, **kwargs: t.Any): 

251 super().__init__(*args, **kwargs) 

252 

253 if arg is None: 

254 KeyError.__init__(self) 

255 else: 

256 KeyError.__init__(self, arg) 

257 

258 @property 

259 def description(self) -> str: 

260 if self.show_exception: 

261 return f"{self._description}\n{KeyError.__name__}: {KeyError.__str__(self)}" 

262 

263 return self._description 

264 

265 @description.setter 

266 def description(self, value: str) -> None: 

267 self._description = value 

268 

269 

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. 

273 

274 .. versionadded:: 0.8 

275 """ 

276 

277 

278class SecurityError(BadRequest): 

279 """The request is invalid in some way that may indicate a security event. 

280 

281 .. versionadded:: 0.9 

282 """ 

283 

284 

285class BadHost(BadRequest): 

286 """The request's ``Host`` header is invalid. 

287 

288 .. versionadded:: 0.11.2 

289 """ 

290 

291 

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. 

297 

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. 

302 

303 .. versionchanged:: 0.15.3 

304 If the ``www_authenticate`` argument is not set, the 

305 ``WWW-Authenticate`` header is not set. 

306 

307 The ``response`` argument was restored. 

308 

309 .. versionchanged:: 0.15.1 

310 ``description`` was moved back as the first argument, restoring 

311 its previous position. 

312 

313 .. versionchanged:: 0.15.0 

314 ``www_authenticate`` was added as the first argument, ahead of 

315 ``description``. 

316 """ 

317 

318 code = 401 

319 description = "Missing or invalid authentication credentials." 

320 

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) 

328 

329 from .datastructures.auth import WWWAuthenticate 

330 

331 if isinstance(www_authenticate, WWWAuthenticate): 

332 www_authenticate = (www_authenticate,) 

333 

334 self.www_authenticate = www_authenticate 

335 

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 

340 

341 

342class Forbidden(HTTPException): 

343 code = 403 

344 description = "Insufficient permissions for the requested resource." 

345 

346 

347class NotFound(HTTPException): 

348 code = 404 

349 description = "The requested resource does not exist." 

350 

351 

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 """ 

358 

359 code = 405 

360 description = "The request method is not allowed." 

361 

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) 

369 

370 if valid_methods is None: 

371 valid_methods = () 

372 

373 self.valid_methods: t.Iterable[str] = valid_methods 

374 

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 

379 

380 

381class NotAcceptable(HTTPException): 

382 code = 406 

383 description = "The client's indicated preferences could not be matched." 

384 

385 

386class RequestTimeout(HTTPException): 

387 code = 408 

388 description = "A complete request was not received in time." 

389 

390 

391class Conflict(HTTPException): 

392 """ 

393 .. versionadded:: 0.7 

394 """ 

395 

396 code = 409 

397 description = "There is a conflict with the current state of the resource." 

398 

399 

400class Gone(HTTPException): 

401 code = 410 

402 description = "The requested resource no longer exists." 

403 

404 

405class LengthRequired(HTTPException): 

406 code = 411 

407 description = "The request must specify its length." 

408 

409 

410class PreconditionFailed(HTTPException): 

411 code = 412 

412 description = ( 

413 "The requested conditions do not match the current state of the resource." 

414 ) 

415 

416 

417class RequestEntityTooLarge(HTTPException): 

418 code = 413 

419 description = "The data is larger than what the server allows." 

420 

421 

422class RequestURITooLarge(HTTPException): 

423 code = 414 

424 description = "The URL is longer than what the server allows." 

425 

426 

427class UnsupportedMediaType(HTTPException): 

428 code = 415 

429 description = "The data format is not supported." 

430 

431 

432class RequestedRangeNotSatisfiable(HTTPException): 

433 """ 

434 .. versionadded:: 0.7 

435 """ 

436 

437 code = 416 

438 description = "The requested range cannot be provided." 

439 

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 

448 

449 super().__init__(description=description, response=response) 

450 self.content_range = ContentRange(units=units, length=length) 

451 

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 

456 

457 

458class ExpectationFailed(HTTPException): 

459 """ 

460 .. versionadded:: 0.7 

461 """ 

462 

463 code = 417 

464 description = "The requested expectations could not be met." 

465 

466 

467class ImATeapot(HTTPException): 

468 """ 

469 .. versionadded:: 0.7 

470 """ 

471 

472 code = 418 

473 description = "The server is a teapot, not a coffee machine." 

474 

475 

476class MisdirectedRequest(HTTPException): 

477 """ 

478 .. versionadded:: 3.1 

479 """ 

480 

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 ) 

486 

487 

488class UnprocessableEntity(HTTPException): 

489 code = 422 

490 description = "The data format is supported, but the data is invalid." 

491 

492 

493class Locked(HTTPException): 

494 code = 423 

495 description = "The resource is currently locked and can't be accessed." 

496 

497 

498class FailedDependency(HTTPException): 

499 code = 424 

500 description = "The current action depends on another action that failed." 

501 

502 

503class PreconditionRequired(HTTPException): 

504 code = 428 

505 description = "The request must contain conditions." 

506 

507 

508class _RetryAfter(HTTPException): 

509 """Adds an optional ``retry_after`` parameter which will set the 

510 ``Retry-After`` header. 

511 """ 

512 

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 

521 

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 

526 

527 

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``. 

532 

533 .. versionchanged:: 1.0 

534 The ``retry_after`` parameter was added. 

535 """ 

536 

537 code = 429 

538 description = "This client has made too many requests too quickly." 

539 

540 

541class RequestHeaderFieldsTooLarge(HTTPException): 

542 code = 431 

543 description = "The headers are larger than what the server allows." 

544 

545 

546class UnavailableForLegalReasons(HTTPException): 

547 code = 451 

548 description = "The resource is not available for legal reasons." 

549 

550 

551class InternalServerError(HTTPException): 

552 """ 

553 :param original_exception: The exception that caused this 500 error. 

554 

555 .. versionchanged:: 1.0.0 

556 The ``original_exception`` parameter and attribute was added. 

557 """ 

558 

559 code = 500 

560 description = "The server encountered an unexpected error." 

561 

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) 

569 

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 """ 

574 

575 

576class NotImplemented(HTTPException): 

577 code = 501 

578 description = "The server does not support the requested action." 

579 

580 

581class BadGateway(HTTPException): 

582 code = 502 

583 description = "The proxy received an invalid response from the upstream server." 

584 

585 

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``. 

590 

591 .. versionchanged:: 1.0 

592 The ``retry_after`` parameter was added. 

593 """ 

594 

595 code = 503 

596 description = "The server is not currently available to handle the request." 

597 

598 

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 ) 

605 

606 

607class HTTPVersionNotSupported(HTTPException): 

608 code = 505 

609 description = "The server does not support the HTTP version used by the client." 

610 

611 

612_default_exceptions: dict[int, type[HTTPException]] = {} 

613 

614 

615def _find_exceptions() -> None: 

616 for obj in globals().values(): 

617 if obj is HTTPException: 

618 continue 

619 

620 try: 

621 is_http_exception = issubclass(obj, HTTPException) 

622 except TypeError: 

623 is_http_exception = False 

624 

625 if not is_http_exception or not obj.code: 

626 continue 

627 

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 

632 

633 # accidentally redefined a status code 

634 raise TypeError(f"{obj} redefines {obj.code} from {old_obj}") 

635 

636 _default_exceptions[obj.code] = obj 

637 

638 

639_find_exceptions() 

640del _find_exceptions 

641 

642 

643class Aborter: 

644 """Map status codes to HTTP exceptions. Used by :func:`abort`. 

645 

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 """ 

651 

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 

659 

660 self.mapping = dict(mapping) 

661 

662 if extra is not None: 

663 self.mapping.update(extra) 

664 

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. 

669 

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 

677 

678 if isinstance(status, Response): 

679 raise HTTPException(response=status) 

680 

681 if status not in self.mapping: 

682 raise LookupError(f"no exception for {status!r}") 

683 

684 raise self.mapping[status](*args, **kwargs) 

685 

686 

687_aborter: Aborter = Aborter() 

688 

689 

690def abort(status: int | WSGIResponse, *args: t.Any, **kwargs: t.Any) -> t.NoReturn: 

691 """Raise an :exc:`.HTTPException` for the given status or response. 

692 

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)