From fbfad9fbf5e061cb7c658dada3c4014c9796021c Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Thu, 10 Nov 2022 23:42:43 -0700 Subject: Inbound and outbound follows basic working --- stator/graph.py | 27 ++++++++++++++++----------- stator/models.py | 20 +++++++++++++++++--- stator/runner.py | 6 +++--- 3 files changed, 36 insertions(+), 17 deletions(-) (limited to 'stator') 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() -- cgit v1.2.3