summaryrefslogtreecommitdiffstats
path: root/stator
diff options
context:
space:
mode:
Diffstat (limited to 'stator')
-rw-r--r--stator/graph.py27
-rw-r--r--stator/models.py20
-rw-r--r--stator/runner.py6
3 files changed, 36 insertions, 17 deletions
diff --git a/stator/graph.py b/stator/graph.py
index 7a8455c..00ef1c4 100644
--- a/stator/graph.py
+++ b/stator/graph.py
@@ -41,6 +41,7 @@ class StateGraph:
initial_state = state
# Collect terminal states
if state.terminal:
+ state.externally_progressed = True
terminal_states.add(state)
# Ensure they do NOT have a handler
try:
@@ -52,17 +53,18 @@ class StateGraph:
f"Terminal state '{state}' should not have a handler method ({state.handler_name})"
)
else:
- # Ensure non-terminal states have a try interval and a handler
- if not state.try_interval:
- raise ValueError(
- f"State '{state}' has no try_interval and is not terminal"
- )
- try:
- state.handler
- except AttributeError:
- raise ValueError(
- f"State '{state}' does not have a handler method ({state.handler_name})"
- )
+ # Ensure non-terminal/manual states have a try interval and a handler
+ if not state.externally_progressed:
+ if not state.try_interval:
+ raise ValueError(
+ f"State '{state}' has no try_interval and is not terminal or manual"
+ )
+ try:
+ state.handler
+ except AttributeError:
+ raise ValueError(
+ f"State '{state}' does not have a handler method ({state.handler_name})"
+ )
if initial_state is None:
raise ValueError("The graph has no initial state")
cls.initial_state = initial_state
@@ -80,9 +82,11 @@ class State:
self,
try_interval: Optional[float] = None,
handler_name: Optional[str] = None,
+ externally_progressed: bool = False,
):
self.try_interval = try_interval
self.handler_name = handler_name
+ self.externally_progressed = externally_progressed
self.parents: Set["State"] = set()
self.children: Set["State"] = set()
@@ -118,6 +122,7 @@ class State:
@property
def handler(self) -> Callable[[Any], Optional[str]]:
+ # Retrieve it by name off the graph
if self.handler_name is None:
raise AttributeError("No handler defined")
return getattr(self.graph, self.handler_name)
diff --git a/stator/models.py b/stator/models.py
index 50ee622..072a3ed 100644
--- a/stator/models.py
+++ b/stator/models.py
@@ -80,7 +80,7 @@ class StatorModel(models.Model):
q = models.Q()
for state in cls.state_graph.states.values():
state = cast(State, state)
- if not state.terminal:
+ if not state.externally_progressed:
q = q | models.Q(
(
models.Q(
@@ -135,17 +135,31 @@ class StatorModel(models.Model):
self.state_ready = True
self.save()
- async def atransition_attempt(self) -> Optional[str]:
+ async def atransition_attempt(self) -> Optional[State]:
"""
Attempts to transition the current state by running its handler(s).
"""
+ current_state = self.state_graph.states[self.state]
+ # If it's a manual progression state don't even try
+ # We shouldn't really be here in this case, but it could be a race condition
+ if current_state.externally_progressed:
+ print("Externally progressed state!")
+ return None
try:
- next_state = await self.state_graph.states[self.state].handler(self)
+ next_state = await current_state.handler(self)
except BaseException as e:
await StatorError.acreate_from_instance(self, e)
traceback.print_exc()
else:
if next_state:
+ # Ensure it's a State object
+ if isinstance(next_state, str):
+ next_state = self.state_graph.states[next_state]
+ # Ensure it's a child
+ if next_state not in current_state.children:
+ raise ValueError(
+ f"Cannot transition from {current_state} to {next_state} - not a declared transition"
+ )
await self.atransition_perform(next_state)
return next_state
await self.__class__.objects.filter(pk=self.pk).aupdate(
diff --git a/stator/runner.py b/stator/runner.py
index 1392e4d..0b42b27 100644
--- a/stator/runner.py
+++ b/stator/runner.py
@@ -50,9 +50,6 @@ class StatorRunner:
min(space_remaining, self.MAX_TASKS_PER_MODEL),
timezone.now() + datetime.timedelta(seconds=self.LOCK_TIMEOUT),
):
- print(
- f"Attempting transition on {instance._meta.label_lower}#{instance.pk}"
- )
self.tasks.append(
asyncio.create_task(self.run_transition(instance))
)
@@ -76,6 +73,9 @@ class StatorRunner:
Wrapper for atransition_attempt with fallback error handling
"""
try:
+ print(
+ f"Attempting transition on {instance._meta.label_lower}#{instance.pk} from state {instance.state}"
+ )
await instance.atransition_attempt()
except BaseException:
traceback.print_exc()