|
1 |
| -from collections.abc import Sequence |
| 1 | +from collections.abc import Mapping, Sequence |
2 | 2 | from typing import Any
|
3 | 3 |
|
4 | 4 | from django.http import HttpRequest, JsonResponse
|
5 | 5 | from django_stubs_ext import StrOrPromise
|
6 | 6 | from rest_framework.renderers import BaseRenderer
|
7 | 7 | from rest_framework.request import Request
|
8 |
| -from typing_extensions import TypeAlias |
9 |
| - |
10 |
| -def _get_error_details(data: Any, default_code: str | None = ...) -> Any: ... |
11 |
| -def _get_codes(detail: Any) -> Any: ... |
12 |
| -def _get_full_details(detail: Any) -> Any: ... |
| 8 | +from typing_extensions import TypeAlias, TypedDict |
13 | 9 |
|
14 | 10 | class ErrorDetail(str):
|
15 | 11 | code: str | None
|
16 | 12 | def __new__(cls, string: str, code: str | None = ...): ...
|
17 | 13 |
|
18 |
| -_Detail: TypeAlias = StrOrPromise | list[Any] | dict[str, Any] |
| 14 | +_Detail: TypeAlias = ErrorDetail | list[ErrorDetail] | dict[str, ErrorDetail] |
| 15 | +# NB! _APIExceptionInput doesn't technically handle Sequence/Mapping, but only list/tuple/dict. |
| 16 | +# But since list/tuple are non-covariant types, we run into issues with union type compatibility for input params. |
| 17 | +# So use the more relaxed Sequence/Mapping for now. |
| 18 | +_APIExceptionInput: TypeAlias = ( |
| 19 | + _Detail | StrOrPromise | Sequence[_APIExceptionInput] | Mapping[str, _APIExceptionInput] | None |
| 20 | +) |
| 21 | +_ErrorCodes: TypeAlias = str | None | list[_ErrorCodes] | dict[str, _ErrorCodes] |
| 22 | + |
| 23 | +class _FullDetailDict(TypedDict): |
| 24 | + message: ErrorDetail |
| 25 | + code: str | None |
| 26 | + |
| 27 | +_ErrorFullDetails: TypeAlias = _FullDetailDict | list[_FullDetailDict] | dict[str, _FullDetailDict] |
| 28 | + |
| 29 | +def _get_error_details(data: _APIExceptionInput, default_code: str | None = ...) -> _Detail: ... |
| 30 | +def _get_codes(detail: _Detail) -> _ErrorCodes: ... |
| 31 | +def _get_full_details(detail: _Detail) -> _ErrorFullDetails: ... |
19 | 32 |
|
20 | 33 | class APIException(Exception):
|
21 | 34 | status_code: int
|
22 |
| - default_detail: _Detail |
| 35 | + default_detail: _APIExceptionInput |
23 | 36 | default_code: str
|
24 | 37 |
|
25 | 38 | detail: _Detail
|
26 |
| - def __init__(self, detail: _Detail | None = ..., code: str | None = ...) -> None: ... |
27 |
| - def get_codes(self) -> Any: ... |
28 |
| - def get_full_details(self) -> Any: ... |
| 39 | + def __init__(self, detail: _APIExceptionInput = ..., code: str | None = ...) -> None: ... |
| 40 | + def get_codes(self) -> _ErrorCodes: ... |
| 41 | + def get_full_details(self) -> _ErrorFullDetails: ... |
| 42 | + |
| 43 | +class ValidationError(APIException): |
| 44 | + # ValidationError always wraps `detail` in a list. |
| 45 | + detail: list[ErrorDetail] | dict[str, ErrorDetail] |
29 | 46 |
|
30 |
| -class ValidationError(APIException): ... |
31 | 47 | class ParseError(APIException): ...
|
32 | 48 | class AuthenticationFailed(APIException): ...
|
33 | 49 | class NotAuthenticated(APIException): ...
|
34 | 50 | class PermissionDenied(APIException): ...
|
35 | 51 | class NotFound(APIException): ...
|
36 | 52 |
|
37 | 53 | class MethodNotAllowed(APIException):
|
38 |
| - def __init__(self, method: str, detail: _Detail | None = ..., code: str | None = ...) -> None: ... |
| 54 | + def __init__(self, method: str, detail: _APIExceptionInput = ..., code: str | None = ...) -> None: ... |
39 | 55 |
|
40 | 56 | class NotAcceptable(APIException):
|
41 | 57 | available_renderers: Sequence[BaseRenderer] | None
|
42 | 58 | def __init__(
|
43 | 59 | self,
|
44 |
| - detail: _Detail | None = ..., |
| 60 | + detail: _APIExceptionInput = ..., |
45 | 61 | code: str | None = ...,
|
46 | 62 | available_renderers: Sequence[BaseRenderer] | None = ...,
|
47 | 63 | ) -> None: ...
|
48 | 64 |
|
49 | 65 | class UnsupportedMediaType(APIException):
|
50 |
| - def __init__(self, media_type: str, detail: _Detail | None = ..., code: str | None = ...) -> None: ... |
| 66 | + def __init__(self, media_type: str, detail: _APIExceptionInput = ..., code: str | None = ...) -> None: ... |
51 | 67 |
|
52 | 68 | class Throttled(APIException):
|
53 | 69 | extra_detail_singular: str
|
54 | 70 | extra_detail_plural: str
|
55 |
| - def __init__(self, wait: float | None = ..., detail: _Detail | None = ..., code: str | None = ...): ... |
| 71 | + def __init__(self, wait: float | None = ..., detail: _APIExceptionInput = ..., code: str | None = ...): ... |
56 | 72 |
|
57 | 73 | def server_error(request: HttpRequest | Request, *args: Any, **kwargs: Any) -> JsonResponse: ...
|
58 | 74 | def bad_request(request: HttpRequest | Request, exception: Exception, *args: Any, **kwargs: Any) -> JsonResponse: ...
|
0 commit comments