python-saga-orchestrator 0.2.3.dev0__py3-none-any.whl → 0.3.0__py3-none-any.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.
- {python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/METADATA +1 -1
- {python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/RECORD +14 -13
- saga_orchestrator/_version.py +2 -2
- saga_orchestrator/domain/models/context.py +0 -1
- saga_orchestrator/domain/models/enums/base_str_enum.py +10 -0
- saga_orchestrator/domain/models/enums/saga_status.py +11 -9
- saga_orchestrator/domain/models/enums/saga_step_phase.py +4 -2
- saga_orchestrator/domain/models/enums/saga_step_status.py +4 -2
- saga_orchestrator/domain/models/step.py +71 -15
- saga_orchestrator/inbox/models.py +1 -2
- saga_orchestrator/outbox/models.py +1 -2
- {python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/WHEEL +0 -0
- {python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/top_level.txt +0 -0
{python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/RECORD
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
python_saga_orchestrator-0.
|
|
1
|
+
python_saga_orchestrator-0.3.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
|
|
2
2
|
saga_orchestrator/__init__.py,sha256=FG7zoYhCzpZC_JEsy3ebDraH2ZZ9q3IGIjTGKqBzcF4,2836
|
|
3
|
-
saga_orchestrator/_version.py,sha256=
|
|
3
|
+
saga_orchestrator/_version.py,sha256=RCY04NY0kBCyn6ttH6lnVL6Gz2gceOrt45f0yDOOU8A,520
|
|
4
4
|
saga_orchestrator/admin/__init__.py,sha256=TKwKTM7IieI4nlMAbJ0O0OI0KPKfwbclVffNjRtIyAg,80
|
|
5
5
|
saga_orchestrator/admin/api.py,sha256=u_eLELUlaHpEiuHqweNHzBwSYCtotERAiOxyVtUMe2I,1715
|
|
6
6
|
saga_orchestrator/core/__init__.py,sha256=EsUqhbO_CgCYZz0yBnf6OUXH3N-_uxMod4A7yGzvbMY,264
|
|
@@ -17,19 +17,20 @@ saga_orchestrator/domain/mixins/saga_step_histrory.py,sha256=phB_oue8ksMMtDiNSER
|
|
|
17
17
|
saga_orchestrator/domain/mixins/types.py,sha256=FlKee4yNq6y1lXX3W_SeE385udNNg-oZqMxqVXZoEpo,2526
|
|
18
18
|
saga_orchestrator/domain/models/__init__.py,sha256=vgUSmwVdPhKNijFZsfmZ7mQZD8DJgCV4s4hMVQZNlwE,853
|
|
19
19
|
saga_orchestrator/domain/models/builder.py,sha256=D3mVYEJT7QJ0e2dNrR2UNHT5xhRK4te6s5WchU57EIA,816
|
|
20
|
-
saga_orchestrator/domain/models/context.py,sha256=
|
|
20
|
+
saga_orchestrator/domain/models/context.py,sha256=LF-6sjB70J3adrLQFz_zgEfRaErJA9Ci5TxIfYfkePA,3885
|
|
21
21
|
saga_orchestrator/domain/models/notify.py,sha256=SuZILSFNAWcUQtjL-I_c4K6VFJXXhD5LLLC2KoMq8dw,837
|
|
22
22
|
saga_orchestrator/domain/models/retry.py,sha256=UM2ZrSGKDZRiPMj0qOGp36xiTr78CVKsceXFFxtiOug,1362
|
|
23
23
|
saga_orchestrator/domain/models/saga_snapshot.py,sha256=ysnBVB2rw059pC07Py_xzAA-Bv7d1h6ZZuubM_Fv0_4,867
|
|
24
|
-
saga_orchestrator/domain/models/step.py,sha256
|
|
24
|
+
saga_orchestrator/domain/models/step.py,sha256=BTR9wGTg3r6dcPaf3WoOyyMzajREOZAf_50psp-0eEg,7052
|
|
25
25
|
saga_orchestrator/domain/models/enums/__init__.py,sha256=IFgCFvhHiQl1MSCN_I_w3O8yR94tZvN5Az8M3V3bXEQ,234
|
|
26
|
-
saga_orchestrator/domain/models/enums/
|
|
27
|
-
saga_orchestrator/domain/models/enums/
|
|
28
|
-
saga_orchestrator/domain/models/enums/
|
|
26
|
+
saga_orchestrator/domain/models/enums/base_str_enum.py,sha256=IsGQHXPav6EA8ACWhFIeEz7Y3PMi497l1Abx_T3mBog,255
|
|
27
|
+
saga_orchestrator/domain/models/enums/saga_status.py,sha256=_yLTAur47_6W-AXbmdXHfHzqp_GGcTBHWBiLITksjR0,390
|
|
28
|
+
saga_orchestrator/domain/models/enums/saga_step_phase.py,sha256=CuRhpy_3lDpUqhnPqNNW2ZcE-iQOUcp_sCTaNPooM0Q,164
|
|
29
|
+
saga_orchestrator/domain/models/enums/saga_step_status.py,sha256=dpqaDpxNgUXk3fV8HCQPnC5gDsru2d_Nki4OryAH-Pg,160
|
|
29
30
|
saga_orchestrator/inbox/__init__.py,sha256=uoLytCCT-2-zr369ZpGmQOB_D63fmwjV9T6sCHYGk64,656
|
|
30
31
|
saga_orchestrator/inbox/contracts.py,sha256=unV7h1Eqil6sZhmpGVE_-9c_voRQhYA3KdE63T79s4U,1768
|
|
31
32
|
saga_orchestrator/inbox/dispatcher.py,sha256=O4Zzi5fRAZIUQqlk3oJof6rl7_CPzvqSVQ_Un-1WkOY,4323
|
|
32
|
-
saga_orchestrator/inbox/models.py,sha256=
|
|
33
|
+
saga_orchestrator/inbox/models.py,sha256=MBID-3wBepnIetwMMAU22sD9olG_LpNu9K0LyOo_m5c,2632
|
|
33
34
|
saga_orchestrator/inbox/repository.py,sha256=BdcVlBW9zvM7WrsL2S3Rmihmn-Ri6gr69L8ji7qqip8,5286
|
|
34
35
|
saga_orchestrator/inbox/retry.py,sha256=jTxg6tZBl7Vllt89Yw-QdOSObYWXrZWjAyW36mCJHdg,590
|
|
35
36
|
saga_orchestrator/outbox/__init__.py,sha256=g-isr2D737wMV1Mf_vEPFoS34YE6n6l09iKLLAL86uU,917
|
|
@@ -37,11 +38,11 @@ saga_orchestrator/outbox/contracts.py,sha256=KhqIDIDvwA26xu4VQJpU6qg2ySmsRAORest
|
|
|
37
38
|
saga_orchestrator/outbox/dispatcher.py,sha256=BX9WTuTLx1gfu-5jV8_ejpi9nnEilnKLYY_BljJkxC8,3196
|
|
38
39
|
saga_orchestrator/outbox/event.py,sha256=Kj-u_JO55jx3YMTpA9arIvmTgKEAxhH2-WQE1eTi4KU,273
|
|
39
40
|
saga_orchestrator/outbox/factory.py,sha256=_p7XT_ulouBhl9J9fcgWsZkhkjmzkpfIs9m6E55UPdA,1743
|
|
40
|
-
saga_orchestrator/outbox/models.py,sha256=
|
|
41
|
+
saga_orchestrator/outbox/models.py,sha256=BZRAxfKXPun_qpOG3rzG3QI6AxsW0cVrio6ec-yZjFI,2333
|
|
41
42
|
saga_orchestrator/outbox/repository.py,sha256=A3nCi5UOwQT9mlEY03BrLidyO2sFOQGgYcE4pFeY-pA,4786
|
|
42
43
|
saga_orchestrator/outbox/retry.py,sha256=9Ygz3I0HK0r9imTYevBgz14TkAZphKiOSf0grJpH99c,599
|
|
43
44
|
saga_orchestrator/outbox/serialization.py,sha256=CDicJS95CHhLP47XukJAgfm0baZ83daWXQQF3MyczDo,1687
|
|
44
|
-
python_saga_orchestrator-0.
|
|
45
|
-
python_saga_orchestrator-0.
|
|
46
|
-
python_saga_orchestrator-0.
|
|
47
|
-
python_saga_orchestrator-0.
|
|
45
|
+
python_saga_orchestrator-0.3.0.dist-info/METADATA,sha256=E7vSU3oOd4EfTRfRQkI_kKfgOUho6nE1chRQVDVypmA,10288
|
|
46
|
+
python_saga_orchestrator-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
47
|
+
python_saga_orchestrator-0.3.0.dist-info/top_level.txt,sha256=XBp_2J8dacJGCoVxIDaUYhSEuOusCN3BD_uhEjBEEBA,18
|
|
48
|
+
python_saga_orchestrator-0.3.0.dist-info/RECORD,,
|
saga_orchestrator/_version.py
CHANGED
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.
|
|
22
|
-
__version_tuple__ = version_tuple = (0,
|
|
21
|
+
__version__ = version = '0.3.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 3, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
from enum import
|
|
1
|
+
from enum import auto
|
|
2
2
|
|
|
3
|
+
from .base_str_enum import BaseStrEnum
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
|
|
6
|
+
class SagaStatus(BaseStrEnum):
|
|
7
|
+
RUNNING = auto()
|
|
8
|
+
SUSPENDED = auto()
|
|
9
|
+
FAILED = auto()
|
|
10
|
+
COMPENSATING = auto()
|
|
11
|
+
COMPLETED = auto()
|
|
12
|
+
COMPENSATING_SUSPENDED = auto()
|
|
13
|
+
COMPENSATED = auto()
|
|
12
14
|
|
|
13
15
|
@property
|
|
14
16
|
def is_terminal(self) -> bool:
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import contextlib
|
|
4
3
|
import inspect
|
|
5
4
|
from collections.abc import Callable
|
|
6
5
|
from dataclasses import dataclass
|
|
@@ -8,6 +7,7 @@ from datetime import timedelta
|
|
|
8
7
|
from typing import (
|
|
9
8
|
Any,
|
|
10
9
|
Generic,
|
|
10
|
+
Mapping,
|
|
11
11
|
TypeAlias,
|
|
12
12
|
TypeVar,
|
|
13
13
|
get_args,
|
|
@@ -60,16 +60,16 @@ class InputContext:
|
|
|
60
60
|
"""
|
|
61
61
|
Возвращает тип ('event_type') последнего полученного события.
|
|
62
62
|
"""
|
|
63
|
-
|
|
64
|
-
return
|
|
65
|
-
return
|
|
63
|
+
if not isinstance(self.context.latest_event_meta, Mapping):
|
|
64
|
+
return None
|
|
65
|
+
return self.context.latest_event_meta.get("event_type")
|
|
66
66
|
|
|
67
67
|
@property
|
|
68
68
|
def latest_event_payload(self) -> Any | None:
|
|
69
69
|
"""
|
|
70
70
|
Возвращает "сырую" полезную нагрузку (payload) последнего полученного события.
|
|
71
71
|
"""
|
|
72
|
-
return self.context.
|
|
72
|
+
return self.context.latest_event
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
RootInputMap: TypeAlias = Callable[[InputContext], InputModelT | dict[str, Any]]
|
|
@@ -102,16 +102,56 @@ class BaseStep(Generic[InputModelT, OutputModelT]):
|
|
|
102
102
|
|
|
103
103
|
def __init_subclass__(cls) -> None:
|
|
104
104
|
super().__init_subclass__()
|
|
105
|
-
if cls is BaseStep:
|
|
105
|
+
if cls is BaseStep or inspect.isabstract(cls):
|
|
106
106
|
return
|
|
107
|
+
generic_base = None
|
|
108
|
+
for base in getattr(cls, "__orig_bases__", []):
|
|
109
|
+
origin = get_origin(base)
|
|
110
|
+
if origin and issubclass(origin, BaseStep):
|
|
111
|
+
generic_base = base
|
|
112
|
+
break
|
|
113
|
+
|
|
114
|
+
if not generic_base:
|
|
115
|
+
raise TypeValidationError(
|
|
116
|
+
f"Could not find generic parameters for {cls.__name__}. "
|
|
117
|
+
f"Ensure it inherits from a parameterized BaseStep, e.g., BaseStep[MyInput, MyOutput]"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
concrete_args = get_args(generic_base)
|
|
121
|
+
if not concrete_args or any(isinstance(arg, TypeVar) for arg in concrete_args):
|
|
122
|
+
raise TypeValidationError(
|
|
123
|
+
f"Step '{cls.__name__}' inherits from a generic Step "
|
|
124
|
+
"but was not parameterized with concrete Input/Output models."
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
concrete_input_model, concrete_output_model = concrete_args
|
|
128
|
+
try:
|
|
129
|
+
hints = get_type_hints(
|
|
130
|
+
cls.execute,
|
|
131
|
+
globalns=inspect.getmodule(cls).__dict__,
|
|
132
|
+
include_extras=True,
|
|
133
|
+
)
|
|
134
|
+
except (AttributeError, NameError) as e:
|
|
135
|
+
raise TypeValidationError(
|
|
136
|
+
f"Could not resolve type hints for '{cls.__name__}.execute'. "
|
|
137
|
+
f"Ensure all types are correctly imported. Original error: {e}"
|
|
138
|
+
)
|
|
107
139
|
|
|
108
|
-
hints = get_type_hints(cls.execute)
|
|
109
140
|
if "inp" not in hints or "return" not in hints:
|
|
110
141
|
raise TypeValidationError(
|
|
111
142
|
f"Step '{cls.__name__}' must type annotate execute(inp) and return type"
|
|
112
143
|
)
|
|
113
|
-
|
|
114
|
-
|
|
144
|
+
|
|
145
|
+
input_annotation = hints["inp"]
|
|
146
|
+
if isinstance(input_annotation, TypeVar):
|
|
147
|
+
input_model = concrete_input_model
|
|
148
|
+
else:
|
|
149
|
+
input_model = input_annotation
|
|
150
|
+
|
|
151
|
+
return_annotation = hints["return"]
|
|
152
|
+
output_model = cls._resolve_output_model(
|
|
153
|
+
return_annotation, concrete_output_model
|
|
154
|
+
)
|
|
115
155
|
if not (inspect.isclass(input_model) and issubclass(input_model, BaseModel)):
|
|
116
156
|
raise TypeValidationError(
|
|
117
157
|
f"Step '{cls.__name__}' input must inherit from pydantic BaseModel"
|
|
@@ -120,22 +160,38 @@ class BaseStep(Generic[InputModelT, OutputModelT]):
|
|
|
120
160
|
cls.output_model = output_model
|
|
121
161
|
|
|
122
162
|
@staticmethod
|
|
123
|
-
def _resolve_output_model(
|
|
163
|
+
def _resolve_output_model(
|
|
164
|
+
annotation: Any, concrete_model: type[BaseModel] | None = None
|
|
165
|
+
) -> type[BaseModel]:
|
|
166
|
+
def find_model_in_args(args_tuple: tuple) -> list[type[BaseModel]]:
|
|
167
|
+
candidates = []
|
|
168
|
+
for arg in args_tuple:
|
|
169
|
+
if isinstance(arg, TypeVar) and concrete_model:
|
|
170
|
+
candidates.append(concrete_model)
|
|
171
|
+
elif inspect.isclass(arg) and issubclass(arg, BaseModel):
|
|
172
|
+
candidates.append(arg)
|
|
173
|
+
return list(dict.fromkeys(candidates))
|
|
174
|
+
|
|
124
175
|
if inspect.isclass(annotation) and issubclass(annotation, BaseModel):
|
|
125
176
|
return annotation
|
|
126
177
|
|
|
127
178
|
origin = get_origin(annotation)
|
|
128
179
|
if origin is None:
|
|
180
|
+
if isinstance(annotation, TypeVar) and concrete_model:
|
|
181
|
+
return concrete_model
|
|
129
182
|
raise TypeValidationError("Step execute return type must include BaseModel")
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
]
|
|
183
|
+
|
|
184
|
+
args = get_args(annotation)
|
|
185
|
+
model_candidates = find_model_in_args(args)
|
|
134
186
|
await_candidates = [arg for arg in args if arg is StepAwaitEvent]
|
|
187
|
+
|
|
135
188
|
if len(model_candidates) == 1 and len(await_candidates) <= 1:
|
|
136
189
|
return model_candidates[0]
|
|
190
|
+
|
|
137
191
|
raise TypeValidationError(
|
|
138
|
-
"Step execute return type must be BaseModel or BaseModel | StepAwaitEvent"
|
|
192
|
+
f"Step execute return type must be BaseModel or BaseModel | StepAwaitEvent. "
|
|
193
|
+
f"Found models: {[m.__name__ for m in model_candidates]}, "
|
|
194
|
+
f"Found await events: {len(await_candidates)}."
|
|
139
195
|
)
|
|
140
196
|
|
|
141
197
|
async def execute(self, inp: InputModelT) -> OutputModelT | StepAwaitEvent:
|
|
@@ -5,9 +5,8 @@ from datetime import datetime
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from sqlalchemy import JSON, DateTime
|
|
8
|
+
from sqlalchemy import JSON, DateTime, Integer, String, Text, UniqueConstraint, func
|
|
9
9
|
from sqlalchemy import Enum as SqlEnum
|
|
10
|
-
from sqlalchemy import Integer, String, Text, UniqueConstraint, func
|
|
11
10
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
12
11
|
from sqlalchemy.ext.mutable import MutableDict
|
|
13
12
|
from sqlalchemy.orm import Mapped, declarative_mixin, mapped_column
|
|
@@ -5,9 +5,8 @@ from datetime import datetime
|
|
|
5
5
|
from enum import Enum
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
-
from sqlalchemy import JSON, DateTime
|
|
8
|
+
from sqlalchemy import JSON, DateTime, Integer, String, Text, func
|
|
9
9
|
from sqlalchemy import Enum as SqlEnum
|
|
10
|
-
from sqlalchemy import Integer, String, Text, func
|
|
11
10
|
from sqlalchemy.dialects.postgresql import JSONB, UUID
|
|
12
11
|
from sqlalchemy.ext.mutable import MutableDict
|
|
13
12
|
from sqlalchemy.orm import Mapped, declarative_mixin, mapped_column
|
{python_saga_orchestrator-0.2.3.dev0.dist-info → python_saga_orchestrator-0.3.0.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|