ringo-task-queue 0.1.0.dev6__py3-none-win_amd64.whl → 0.1.0.dev7__py3-none-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,159 +1,159 @@
1
- """Public exception hierarchy for the Ringo Task Queue Python SDK.
2
-
3
- All SDK transport failures derive from :class:`RingoError` and carry one of
4
- the stable public codes from ``docs/contracts/errors.md``. gRPC status
5
- details (``ringo.v1.ErrorDetail``) are preferred over the coarser gRPC code,
6
- exactly as the error contract requires.
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Mapping
12
-
13
- import grpc
14
- from grpc_status import rpc_status
15
-
16
- from ._proto.ringo.v1 import queue_pb2
17
-
18
-
19
- class RingoError(Exception):
20
- """Base class for every SDK-raised transport/protocol error."""
21
-
22
- #: Stable public code (see docs/contracts/errors.md).
23
- code = "INTERNAL"
24
-
25
- def __init__(
26
- self,
27
- message: str = "",
28
- *,
29
- request_id: str | None = None,
30
- metadata: Mapping[str, str] | None = None,
31
- grpc_code: grpc.StatusCode | None = None,
32
- ) -> None:
33
- super().__init__(message)
34
- self.message = message
35
- self.request_id = request_id
36
- self.metadata = dict(metadata) if metadata else {}
37
- self.grpc_code = grpc_code
38
-
39
- def __repr__(self) -> str: # pragma: no cover - debugging aid
40
- return f"{type(self).__name__}(code={self.code!r}, message={self.message!r})"
41
-
42
-
43
- class InvalidArgumentError(RingoError):
44
- code = "INVALID_ARGUMENT"
45
-
46
-
47
- class DuplicateError(RingoError):
48
- code = "DUPLICATE"
49
-
50
-
51
- class NotFoundError(RingoError):
52
- code = "NOT_FOUND"
53
-
54
-
55
- class ConflictError(RingoError):
56
- code = "CONFLICT"
57
-
58
-
59
- class LeaseLostError(RingoError):
60
- code = "LEASE_LOST"
61
-
62
-
63
- class UnavailableError(RingoError):
64
- code = "UNAVAILABLE"
65
-
66
-
67
- class DeadlineExceededError(RingoError):
68
- code = "DEADLINE_EXCEEDED"
69
-
70
-
71
- class IncompatibleVersionError(RingoError):
72
- code = "INCOMPATIBLE_VERSION"
73
-
74
-
75
- class InternalError(RingoError):
76
- code = "INTERNAL"
77
-
78
-
79
- _CODE_TO_ERROR: dict[int, type[RingoError]] = {
80
- queue_pb2.ERROR_CODE_INVALID_ARGUMENT: InvalidArgumentError,
81
- queue_pb2.ERROR_CODE_DUPLICATE: DuplicateError,
82
- queue_pb2.ERROR_CODE_NOT_FOUND: NotFoundError,
83
- queue_pb2.ERROR_CODE_CONFLICT: ConflictError,
84
- queue_pb2.ERROR_CODE_LEASE_LOST: LeaseLostError,
85
- queue_pb2.ERROR_CODE_UNAVAILABLE: UnavailableError,
86
- queue_pb2.ERROR_CODE_DEADLINE_EXCEEDED: DeadlineExceededError,
87
- queue_pb2.ERROR_CODE_INCOMPATIBLE_VERSION: IncompatibleVersionError,
88
- queue_pb2.ERROR_CODE_INTERNAL: InternalError,
89
- }
90
-
91
- # gRPC codes that map unambiguously onto a single public Ringo code. Other
92
- # gRPC codes raise the base RingoError without inventing a more specific
93
- # public code (docs/contracts/errors.md).
94
- _GRPC_FALLBACK: dict[grpc.StatusCode, type[RingoError]] = {
95
- grpc.StatusCode.INVALID_ARGUMENT: InvalidArgumentError,
96
- grpc.StatusCode.NOT_FOUND: NotFoundError,
97
- grpc.StatusCode.ALREADY_EXISTS: DuplicateError,
98
- grpc.StatusCode.ABORTED: LeaseLostError,
99
- grpc.StatusCode.UNAVAILABLE: UnavailableError,
100
- grpc.StatusCode.DEADLINE_EXCEEDED: DeadlineExceededError,
101
- grpc.StatusCode.INTERNAL: InternalError,
102
- }
103
-
104
-
105
- def error_from_detail(detail: queue_pb2.ErrorDetail) -> RingoError:
106
- """Build the mapped exception from a ``ringo.v1.ErrorDetail`` message."""
107
- cls = _CODE_TO_ERROR.get(detail.code, RingoError)
108
- return cls(
109
- detail.message,
110
- request_id=detail.request_id or None,
111
- metadata=detail.metadata,
112
- )
113
-
114
-
115
- def map_rpc_error(exc: grpc.aio.AioRpcError) -> RingoError:
116
- """Map a gRPC call failure to the stable public exception.
117
-
118
- Prefers the ``ringo.v1.ErrorDetail`` serialized in ``google.rpc.Status``
119
- details; falls back to the gRPC code only when details are unavailable.
120
- """
121
- rich = None
122
- try:
123
- rich = rpc_status.from_call(exc)
124
- except Exception:
125
- rich = None
126
- if rich is not None:
127
- for any_msg in rich.details:
128
- if any_msg.Is(queue_pb2.ErrorDetail.DESCRIPTOR):
129
- detail = queue_pb2.ErrorDetail()
130
- any_msg.Unpack(detail)
131
- err = error_from_detail(detail)
132
- err.grpc_code = exc.code()
133
- return err
134
- cls = _GRPC_FALLBACK.get(exc.code(), RingoError)
135
- message = exc.details() or str(exc)
136
- return cls(message, grpc_code=exc.code())
137
-
138
-
139
- # ---------------------------------------------------------------------------
140
- # Handler signalling errors (execution outcomes, not transport errors).
141
-
142
-
143
- class RetryTaskError(Exception):
144
- """Raise from a handler to retry with an optional per-attempt delay."""
145
-
146
- def __init__(
147
- self,
148
- message: str = "",
149
- *,
150
- delay: float | None = None,
151
- congestion: bool = False,
152
- ) -> None:
153
- super().__init__(message)
154
- self.delay = delay
155
- self.congestion = congestion
156
-
157
-
158
- class PermanentTaskError(Exception):
159
- """Raise from a handler to mark the task permanently failed (DEAD)."""
1
+ """Public exception hierarchy for the Ringo Task Queue Python SDK.
2
+
3
+ All SDK transport failures derive from :class:`RingoError` and carry one of
4
+ the stable public codes from ``docs/contracts/errors.md``. gRPC status
5
+ details (``ringo.v1.ErrorDetail``) are preferred over the coarser gRPC code,
6
+ exactly as the error contract requires.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Mapping
12
+
13
+ import grpc
14
+ from grpc_status import rpc_status
15
+
16
+ from ._proto.ringo.v1 import queue_pb2
17
+
18
+
19
+ class RingoError(Exception):
20
+ """Base class for every SDK-raised transport/protocol error."""
21
+
22
+ #: Stable public code (see docs/contracts/errors.md).
23
+ code = "INTERNAL"
24
+
25
+ def __init__(
26
+ self,
27
+ message: str = "",
28
+ *,
29
+ request_id: str | None = None,
30
+ metadata: Mapping[str, str] | None = None,
31
+ grpc_code: grpc.StatusCode | None = None,
32
+ ) -> None:
33
+ super().__init__(message)
34
+ self.message = message
35
+ self.request_id = request_id
36
+ self.metadata = dict(metadata) if metadata else {}
37
+ self.grpc_code = grpc_code
38
+
39
+ def __repr__(self) -> str: # pragma: no cover - debugging aid
40
+ return f"{type(self).__name__}(code={self.code!r}, message={self.message!r})"
41
+
42
+
43
+ class InvalidArgumentError(RingoError):
44
+ code = "INVALID_ARGUMENT"
45
+
46
+
47
+ class DuplicateError(RingoError):
48
+ code = "DUPLICATE"
49
+
50
+
51
+ class NotFoundError(RingoError):
52
+ code = "NOT_FOUND"
53
+
54
+
55
+ class ConflictError(RingoError):
56
+ code = "CONFLICT"
57
+
58
+
59
+ class LeaseLostError(RingoError):
60
+ code = "LEASE_LOST"
61
+
62
+
63
+ class UnavailableError(RingoError):
64
+ code = "UNAVAILABLE"
65
+
66
+
67
+ class DeadlineExceededError(RingoError):
68
+ code = "DEADLINE_EXCEEDED"
69
+
70
+
71
+ class IncompatibleVersionError(RingoError):
72
+ code = "INCOMPATIBLE_VERSION"
73
+
74
+
75
+ class InternalError(RingoError):
76
+ code = "INTERNAL"
77
+
78
+
79
+ _CODE_TO_ERROR: dict[int, type[RingoError]] = {
80
+ queue_pb2.ERROR_CODE_INVALID_ARGUMENT: InvalidArgumentError,
81
+ queue_pb2.ERROR_CODE_DUPLICATE: DuplicateError,
82
+ queue_pb2.ERROR_CODE_NOT_FOUND: NotFoundError,
83
+ queue_pb2.ERROR_CODE_CONFLICT: ConflictError,
84
+ queue_pb2.ERROR_CODE_LEASE_LOST: LeaseLostError,
85
+ queue_pb2.ERROR_CODE_UNAVAILABLE: UnavailableError,
86
+ queue_pb2.ERROR_CODE_DEADLINE_EXCEEDED: DeadlineExceededError,
87
+ queue_pb2.ERROR_CODE_INCOMPATIBLE_VERSION: IncompatibleVersionError,
88
+ queue_pb2.ERROR_CODE_INTERNAL: InternalError,
89
+ }
90
+
91
+ # gRPC codes that map unambiguously onto a single public Ringo code. Other
92
+ # gRPC codes raise the base RingoError without inventing a more specific
93
+ # public code (docs/contracts/errors.md).
94
+ _GRPC_FALLBACK: dict[grpc.StatusCode, type[RingoError]] = {
95
+ grpc.StatusCode.INVALID_ARGUMENT: InvalidArgumentError,
96
+ grpc.StatusCode.NOT_FOUND: NotFoundError,
97
+ grpc.StatusCode.ALREADY_EXISTS: DuplicateError,
98
+ grpc.StatusCode.ABORTED: LeaseLostError,
99
+ grpc.StatusCode.UNAVAILABLE: UnavailableError,
100
+ grpc.StatusCode.DEADLINE_EXCEEDED: DeadlineExceededError,
101
+ grpc.StatusCode.INTERNAL: InternalError,
102
+ }
103
+
104
+
105
+ def error_from_detail(detail: queue_pb2.ErrorDetail) -> RingoError:
106
+ """Build the mapped exception from a ``ringo.v1.ErrorDetail`` message."""
107
+ cls = _CODE_TO_ERROR.get(detail.code, RingoError)
108
+ return cls(
109
+ detail.message,
110
+ request_id=detail.request_id or None,
111
+ metadata=detail.metadata,
112
+ )
113
+
114
+
115
+ def map_rpc_error(exc: grpc.aio.AioRpcError) -> RingoError:
116
+ """Map a gRPC call failure to the stable public exception.
117
+
118
+ Prefers the ``ringo.v1.ErrorDetail`` serialized in ``google.rpc.Status``
119
+ details; falls back to the gRPC code only when details are unavailable.
120
+ """
121
+ rich = None
122
+ try:
123
+ rich = rpc_status.from_call(exc)
124
+ except Exception:
125
+ rich = None
126
+ if rich is not None:
127
+ for any_msg in rich.details:
128
+ if any_msg.Is(queue_pb2.ErrorDetail.DESCRIPTOR):
129
+ detail = queue_pb2.ErrorDetail()
130
+ any_msg.Unpack(detail)
131
+ err = error_from_detail(detail)
132
+ err.grpc_code = exc.code()
133
+ return err
134
+ cls = _GRPC_FALLBACK.get(exc.code(), RingoError)
135
+ message = exc.details() or str(exc)
136
+ return cls(message, grpc_code=exc.code())
137
+
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # Handler signalling errors (execution outcomes, not transport errors).
141
+
142
+
143
+ class RetryTaskError(Exception):
144
+ """Raise from a handler to retry with an optional per-attempt delay."""
145
+
146
+ def __init__(
147
+ self,
148
+ message: str = "",
149
+ *,
150
+ delay: float | None = None,
151
+ congestion: bool = False,
152
+ ) -> None:
153
+ super().__init__(message)
154
+ self.delay = delay
155
+ self.congestion = congestion
156
+
157
+
158
+ class PermanentTaskError(Exception):
159
+ """Raise from a handler to mark the task permanently failed (DEAD)."""