From d4d237ec4501a158845607d2720d7ed766f8dbee Mon Sep 17 00:00:00 2001 From: Jesse Johnson Date: Sun, 20 Apr 2025 19:08:30 +0200 Subject: [PATCH 1/2] test: update type hints --- src/superstate/config/system.py | 10 ++- src/superstate/context.py | 22 ++--- src/superstate/machine.py | 48 +++++------ src/superstate/model/action.py | 30 +++---- src/superstate/model/base.py | 18 ++-- src/superstate/model/data.py | 18 ++-- src/superstate/model/system.py | 12 +-- src/superstate/state.py | 142 ++++++++++++++++---------------- src/superstate/transition.py | 16 ++-- src/superstate/types.py | 12 +-- 10 files changed, 166 insertions(+), 162 deletions(-) diff --git a/src/superstate/config/system.py b/src/superstate/config/system.py index eb56623..4232290 100644 --- a/src/superstate/config/system.py +++ b/src/superstate/config/system.py @@ -1,5 +1,7 @@ """Provide system info for statechart components.""" +from __future__ import annotations + from typing_extensions import NotRequired, TypedDict @@ -37,7 +39,7 @@ class TimeInfo(TypedDict): class SystemInfo(TypedDict): """Provide system info.""" - host: NotRequired['HostInfo'] - time: NotRequired['TimeInfo'] - runtime: NotRequired['RuntimeInfo'] - platform: NotRequired['PlatformInfo'] + host: NotRequired[HostInfo] + time: NotRequired[TimeInfo] + runtime: NotRequired[RuntimeInfo] + platform: NotRequired[PlatformInfo] diff --git a/src/superstate/context.py b/src/superstate/context.py index ec7cb2d..3121dfe 100644 --- a/src/superstate/context.py +++ b/src/superstate/context.py @@ -1,5 +1,7 @@ """Provide context for distributing settings within statechart.""" +from __future__ import annotations + import os from typing import TYPE_CHECKING, List, Tuple from uuid import UUID @@ -18,7 +20,7 @@ class Context: """Provide context for statechart.""" - def __init__(self, root: 'State') -> None: + def __init__(self, root: State) -> None: """Initialize a statechart from root state.""" self.__sessionid = UUID( bytes=os.urandom(16), version=4 # pylint: disable=no-member @@ -32,29 +34,29 @@ def _sessionid(self) -> str: return str(self.__sessionid) @property - def current_state(self) -> 'State': + def current_state(self) -> State: """Return the current state.""" # TODO: rename to "head" or "position" return self.__current_state @current_state.setter - def current_state(self, state: 'State') -> None: + def current_state(self, state: State) -> None: """Return the current state.""" # TODO: rename to "head" or "position" self.__current_state = state @property - def root(self) -> 'State': + def root(self) -> State: """Return root state of statechart.""" return self.__root @property - def parent(self) -> 'State': + def parent(self) -> State: """Return parent.""" return self.current_state.parent or self.root @property - def children(self) -> Tuple['State', ...]: + def children(self) -> Tuple[State, ...]: """Return list of states.""" return ( tuple(self.__current_state.states.values()) @@ -63,19 +65,19 @@ def children(self) -> Tuple['State', ...]: ) @property - def states(self) -> Tuple['State', ...]: + def states(self) -> Tuple[State, ...]: """Return list of states.""" return tuple(self.parent.states.values()) @property - def siblings(self) -> Tuple['State', ...]: + def siblings(self) -> Tuple[State, ...]: """Return list of states.""" return tuple(self.parent.states.values()) @property - def active(self) -> Tuple['State', ...]: + def active(self) -> Tuple[State, ...]: """Return active states.""" - states: List['State'] = [] + states: List[State] = [] parents = list(reversed(self.current_state)) for i, x in enumerate(parents): n = i + 1 diff --git a/src/superstate/machine.py b/src/superstate/machine.py index 74cd30a..1d92b67 100644 --- a/src/superstate/machine.py +++ b/src/superstate/machine.py @@ -1,5 +1,7 @@ """Provide parent core statechart capability.""" +from __future__ import annotations + import logging import logging.config import os @@ -48,11 +50,11 @@ class MetaStateChart(type): """Instantiate statecharts from class metadata.""" __name__: str - __initial__: 'Initial' + __initial__: Initial __binding__: str = cast(str, Selection('early', 'late')) __datamodel__: str - _root: 'SubstateMixin' - datamodel: 'DataModel' + _root: SubstateMixin + datamodel: DataModel def __new__( mcs, @@ -106,9 +108,9 @@ class StateChart(metaclass=MetaStateChart): # __slots__ = [ # '__dict__', '__current_state', '__parent', '__root', 'initial' # ] - __root: 'SubstateMixin' - __parent: 'SubstateMixin' - __current_state: 'State' + __root: SubstateMixin + __parent: SubstateMixin + __current_state: State # # System Variables # _name: str @@ -195,23 +197,23 @@ def initial(self) -> Optional[str]: return self.__initial__ @property - def current_state(self) -> 'State': + def current_state(self) -> State: """Return the current state.""" # TODO: rename to head or position potentially return self.__current_state @property - def root(self) -> 'SubstateMixin': + def root(self) -> SubstateMixin: """Return root state of statechart.""" return self.__root @property - def parent(self) -> 'SubstateMixin': + def parent(self) -> SubstateMixin: """Return parent.""" return self.current_state.parent or self.root @property - def children(self) -> Tuple['State', ...]: + def children(self) -> Tuple[State, ...]: """Return list of states.""" return ( tuple(self.__current_state.states.values()) @@ -220,19 +222,19 @@ def children(self) -> Tuple['State', ...]: ) @property - def states(self) -> Tuple['State', ...]: + def states(self) -> Tuple[State, ...]: """Return list of states.""" return tuple(self.parent.states.values()) @property - def siblings(self) -> Tuple['State', ...]: + def siblings(self) -> Tuple[State, ...]: """Return list of states.""" return tuple(self.parent.states.values()) @property - def active(self) -> Tuple['State', ...]: + def active(self) -> Tuple[State, ...]: """Return active states.""" - states: List['State'] = [] + states: List[State] = [] parents = list(reversed(self.current_state)) for i, x in enumerate(parents): n = i + 1 @@ -299,9 +301,9 @@ def change_state(self, statepath: str) -> None: # print('state transition not complete') log.info('changed state to %s', statepath) - def get_state(self, statepath: str) -> 'State': + def get_state(self, statepath: str) -> State: """Get state.""" - state: 'State' = self.root + state: State = self.root macrostep = statepath.split('.') # general recursive search for single query @@ -341,7 +343,7 @@ def get_state(self, statepath: str) -> 'State': raise InvalidState(f"state could not be found: {statepath}") def add_state( - self, state: 'State', statepath: Optional[str] = None + self, state: State, statepath: Optional[str] = None ) -> None: """Add state to either parent or target state.""" parent = self.get_state(statepath) if statepath else self.parent @@ -354,7 +356,7 @@ def add_state( ) @property - def transitions(self) -> Tuple['Transition', ...]: + def transitions(self) -> Tuple[Transition, ...]: """Return list of current transitions.""" return ( tuple(self.current_state.transitions) @@ -363,7 +365,7 @@ def transitions(self) -> Tuple['Transition', ...]: ) def add_transition( - self, transition: 'Transition', statepath: Optional[str] = None + self, transition: Transition, statepath: Optional[str] = None ) -> None: """Add transition to either parent or target state.""" target = self.get_state(statepath) if statepath else self.parent @@ -374,7 +376,7 @@ def add_transition( raise InvalidState('cannot add transition to %s', target) @staticmethod - def _lookup_transitions(event: str, state: 'State') -> List["Transition"]: + def _lookup_transitions(event: str, state: State) -> List[Transition]: return ( state.get_transition(event) if hasattr(state, 'get_transition') @@ -383,13 +385,13 @@ def _lookup_transitions(event: str, state: 'State') -> List["Transition"]: def process_transitions( self, event: str, /, *args: Any, **kwargs: Any - ) -> 'Transition': + ) -> Transition: """Get transition event from active states.""" # TODO: must use datamodel to process transitions # child => parent => grandparent - guarded: List['Transition'] = [] + guarded: List[Transition] = [] for current in self.active: - transitions: List['Transition'] = [] + transitions: List[Transition] = [] # search parallel states for transitions if isinstance(current, ParallelState): diff --git a/src/superstate/model/action.py b/src/superstate/model/action.py index 3c6ae9a..77ec5db 100644 --- a/src/superstate/model/action.py +++ b/src/superstate/model/action.py @@ -1,5 +1,7 @@ """Provide common types for statechart components.""" +from __future__ import annotations + import logging import logging.config from dataclasses import InitVar, asdict, dataclass @@ -33,9 +35,7 @@ class Assign(Action): location: str expr: Optional[Expression] = None # expression - def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any - ) -> None: + def callback(self, provider: Provider, *args: Any, **kwargs: Any) -> None: """Provide callback from datamodel provider.""" kwargs['__mode__'] = 'single' result = provider.exec(self.expr, *args, **kwargs) @@ -65,9 +65,7 @@ class ForEach(Action): def __post_init__(self, content: List[str]) -> None: self.__content = [Action.create(x) for x in content] # type: ignore - def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any - ) -> None: + def callback(self, provider: Provider, *args: Any, **kwargs: Any) -> None: """Provide callback from datamodel provider.""" array = provider.ctx.current_state.datamodel[self.array] if array: @@ -95,9 +93,7 @@ class Log(Action): # def __post_init__(self) -> None: # self.__log = logging.getLogger(self.label or provider.ctx.__name__) - def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any - ) -> None: + def callback(self, provider: Provider, *args: Any, **kwargs: Any) -> None: """Provide callback from datamodel provider.""" kwargs['__mode__'] = 'single' logger = logging.getLogger(self.label) @@ -110,11 +106,9 @@ def callback( class Raise(Action): """Data item providing state data.""" - event: 'Event' + event: Event - def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any - ) -> None: + def callback(self, provider: Provider, *args: Any, **kwargs: Any) -> None: """Provide callback from datamodel provider.""" kwargs['__mode__'] = 'single' @@ -128,7 +122,7 @@ class Script(Action): src: Union[Callable, str] def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any + self, provider: Provider, *args: Any, **kwargs: Any ) -> Optional[Any]: """Provide callback from datamodel provider.""" # need ability to download src URI @@ -140,13 +134,13 @@ def callback( class If(Conditional): """Data item providing state data.""" - content: Sequence['ExecutableContent'] + content: Sequence[ExecutableContent] def __post_init__(self) -> None: self.content = [Action.create(x) for x in self.content] def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any + self, provider: Provider, *args: Any, **kwargs: Any ) -> Optional[Any]: """Provide callback from datamodel provider.""" if provider.eval(self.cond, *args, **kwargs): @@ -170,9 +164,7 @@ def __post_init__(self) -> None: self.cond = True self.content = [Action.create(x) for x in self.content] # type: ignore - def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any - ) -> None: + def callback(self, provider: Provider, *args: Any, **kwargs: Any) -> None: """Provide callback from datamodel provider.""" for action in self.content: provider.handle(action, *args, **kwargs) diff --git a/src/superstate/model/base.py b/src/superstate/model/base.py index c4b44c2..8e232fb 100644 --- a/src/superstate/model/base.py +++ b/src/superstate/model/base.py @@ -1,5 +1,7 @@ """Provide common types for statechart components.""" +from __future__ import annotations + from dataclasses import dataclass from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Union @@ -16,8 +18,8 @@ class ExecutableContent: @classmethod def create( - cls, settings: Union['ExecutableContent', Callable, Dict[str, Any]] - ) -> 'ExecutableContent': + cls, settings: Union[ExecutableContent, Callable, Dict[str, Any]] + ) -> ExecutableContent: """Create expression from configuration.""" if isinstance(settings, ExecutableContent): return settings @@ -36,7 +38,7 @@ def create( raise InvalidConfig('could not find a valid configuration for action') def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any + self, provider: Provider, *args: Any, **kwargs: Any ) -> Optional[Any]: """Provide callback for language provider.""" @@ -48,8 +50,8 @@ class Action(ExecutableContent): @classmethod def create( - cls, settings: Union['ExecutableContent', Callable, Dict[str, Any]] - ) -> 'ExecutableContent': + cls, settings: Union[ExecutableContent, Callable, Dict[str, Any]] + ) -> ExecutableContent: """Create action from configuration.""" if isinstance(settings, str) or callable(settings): for Subclass in lookup_subclasses(cls): @@ -66,15 +68,15 @@ class Conditional(ExecutableContent): @classmethod def create( - cls, settings: Union['ExecutableContent', Callable, Dict[str, Any]] - ) -> 'ExecutableContent': + cls, settings: Union[ExecutableContent, Callable, Dict[str, Any]] + ) -> ExecutableContent: """Create state from configuration.""" if isinstance(settings, (bool, str)) or callable(settings): return cls(settings) # type: ignore return super().create(settings) def callback( - self, provider: 'Provider', *args: Any, **kwargs: Any + self, provider: Provider, *args: Any, **kwargs: Any ) -> Optional[Any]: """Provide callback for language provider.""" return provider.eval(self.cond, *args, **kwargs) diff --git a/src/superstate/model/data.py b/src/superstate/model/data.py index 446b422..37fdead 100644 --- a/src/superstate/model/data.py +++ b/src/superstate/model/data.py @@ -1,5 +1,7 @@ """Provide common types for statechart components.""" +from __future__ import annotations + import json from collections import ChainMap from dataclasses import InitVar, dataclass @@ -45,7 +47,7 @@ def __post_init__(self, settings: Any) -> None: ) @classmethod - def create(cls, settings: Union['Data', dict]) -> 'Data': + def create(cls, settings: Union[Data, dict]) -> Data: """Return data object for data mapper.""" if isinstance(settings, Data): return settings @@ -85,17 +87,17 @@ def value(self) -> Optional[Any]: class DataModel(ChainMap): """Instantiate state types from class metadata.""" - data: List['Data'] + data: List[Data] binding: ClassVar[str] = 'early' - provider: ClassVar[Type['Provider']] = Default + provider: ClassVar[Type[Provider]] = Default def __post_init__(self) -> None: """Validate the data object.""" - self.__parent: Optional['State'] = None - # self.__provider: Optional['Provider'] = None + self.__parent: Optional[State] = None + # self.__provider: Optional[Provider] = None @classmethod - def create(cls, settings: Union['DataModel', dict]) -> 'DataModel': + def create(cls, settings: Union[DataModel, dict]) -> DataModel: """Return data model for data mapper.""" if isinstance(settings, DataModel): return settings @@ -108,12 +110,12 @@ def create(cls, settings: Union['DataModel', dict]) -> 'DataModel': raise InvalidConfig('could not find a valid data model configuration') @property - def parent(self) -> Optional['State']: + def parent(self) -> Optional[State]: """Get parent state.""" return self.__parent @parent.setter - def parent(self, state: 'State') -> None: + def parent(self, state: State) -> None: if self.__parent is None: self.__parent = state # self.maps.insert(0, self.parent.datamodel) diff --git a/src/superstate/model/system.py b/src/superstate/model/system.py index 462d1a1..6a4eeeb 100644 --- a/src/superstate/model/system.py +++ b/src/superstate/model/system.py @@ -1,5 +1,7 @@ """Provide system info for statechart components.""" +from __future__ import annotations + from dataclasses import dataclass, field from typing import TYPE_CHECKING, Optional, cast @@ -14,14 +16,14 @@ class Event: """Represent a system event.""" name: str = cast(str, Identifier()) - kind: 'Selection' = field( + kind: Selection = field( default=Selection('platorm', 'internal', 'external') ) sendid: str = field(default=cast(str, Identifier())) origin: Optional[str] = None # URI origintype: Optional[str] = None invokeid: Optional[str] = None - data: Optional['DataModel'] = None + data: Optional[DataModel] = None @dataclass @@ -29,7 +31,7 @@ class SystemSettings: """Provide system settings.""" _name: str - _event: 'Event' + _event: Event _sessionid: str - # _ioprocessors: Sequence['Processor'] - _x: Optional['DataModel'] = None + # _ioprocessors: Sequence[Processor] + _x: Optional[DataModel] = None diff --git a/src/superstate/state.py b/src/superstate/state.py index a1a4414..1cf40b4 100644 --- a/src/superstate/state.py +++ b/src/superstate/state.py @@ -1,5 +1,7 @@ """Provide states for statechart.""" +from __future__ import annotations + import logging from itertools import chain # , zip_longest from typing import ( @@ -35,12 +37,12 @@ # class MetaState(type): # """Instantiate state types from class metadata.""" # -# initial: Optional['Initial'] +# initial: Optional[Initial] # kind: Optional[str] -# states: List['State'] -# transitions: List['State'] -# on_entry: Optional['ActionTypes'] -# on_exit: Optional['ActionTypes'] +# states: List[State] +# transitions: List[State] +# on_entry: Optional[ActionTypes] +# on_exit: Optional[ActionTypes] # # def __new__( # cls, @@ -68,9 +70,9 @@ class TransitionMixin: """Provide an atomic state for a statechart.""" - __transitions: List['Transition'] + __transitions: List[Transition] - def __register_transition_callback(self, transition: 'Transition') -> None: + def __register_transition_callback(self, transition: Transition) -> None: # XXX: currently mapping to class instead of instance setattr( self, @@ -79,30 +81,30 @@ def __register_transition_callback(self, transition: 'Transition') -> None: transition.callback().__get__(self, self.__class__), ) - def _process_transient_state(self, ctx: 'StateChart') -> None: + def _process_transient_state(self, ctx: StateChart) -> None: for transition in self.transitions: if transition.event == '': ctx._auto_() # pylint: disable=protected-access break @property - def transitions(self) -> Tuple['Transition', ...]: + def transitions(self) -> Tuple[Transition, ...]: """Return transitions of this state.""" return tuple(self.__transitions) @transitions.setter - def transitions(self, transitions: List['Transition']) -> None: + def transitions(self, transitions: List[Transition]) -> None: """Initialize atomic state.""" self.__transitions = transitions for transition in self.transitions: self.__register_transition_callback(transition) - def add_transition(self, transition: 'Transition') -> None: + def add_transition(self, transition: Transition) -> None: """Add transition to this state.""" self.__transitions.append(transition) self.__register_transition_callback(transition) - def get_transition(self, event: str) -> Tuple['Transition', ...]: + def get_transition(self, event: str) -> Tuple[Transition, ...]: """Get each transition maching event.""" return tuple( filter( @@ -115,56 +117,50 @@ class ContentMixin: """Provide an atomic state for a statechart.""" name: str - __on_entry: Optional['ActionTypes'] - __on_exit: Optional['ActionTypes'] + __on_entry: Optional[ActionTypes] + __on_exit: Optional[ActionTypes] @property - def on_entry(self) -> Optional['ActionTypes']: + def on_entry(self) -> Optional[ActionTypes]: """Return on-entry content of this state.""" return self.__on_entry @on_entry.setter - def on_entry(self, content: 'ActionTypes') -> None: + def on_entry(self, content: ActionTypes) -> None: """Set on-entry content of this state.""" self.__on_entry = content @property - def on_exit(self) -> Optional['ActionTypes']: + def on_exit(self) -> Optional[ActionTypes]: """Return on-exit content of this state.""" return self.__on_entry @on_exit.setter - def on_exit(self, content: 'ActionTypes') -> None: + def on_exit(self, content: ActionTypes) -> None: """Set on-exit content of this state.""" self.__on_exit = content - def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_entry(self, ctx: StateChart) -> Optional[Any]: """Run on-entry tasks.""" if self.__on_entry: results = [] executor = ctx.datamodel.provider(ctx) for expression in self.__on_entry: - results.append( - executor.handle(expression) - ) # *args, **kwargs)) + results.append(executor.handle(expression)) # *args, **kwargs)) log.info( "executed 'on_entry' state change action for %s", self.name ) return results return None - def run_on_exit(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_exit(self, ctx: StateChart) -> Optional[Any]: """Run on-exit tasks.""" if self.__on_exit: results = [] executor = ctx.datamodel.provider(ctx) for expression in self.__on_exit: - results.append( - executor.handle(expression) - ) # *args, **kwargs)) - log.info( - "executed 'on_exit' state change action for %s", self.name - ) + results.append(executor.handle(expression)) # *args, **kwargs)) + log.info("executed 'on_exit' state change action for %s", self.name) return results return None @@ -183,18 +179,18 @@ class State: # '__type', # ] - __stack: List['State'] - datamodel: 'DataModel' + __stack: List[State] + datamodel: DataModel name: str = cast(str, Identifier()) # history: Optional['HistoryState'] - # final: Optional['FinalState'] - states: Dict[str, 'State'] - # transitions: Tuple['Transition', ...] - # onentry: Tuple['ActionTypes', ...] - # onexit: Tuple['ActionTypes', ...] + # final: Optional[FinalState] + states: Dict[str, State] + # transitions: Tuple[Transition, ...] + # onentry: Tuple[ActionTypes, ...] + # onexit: Tuple[ActionTypes, ...] # pylint: disable-next=unused-argument - def __new__(cls, *args: Any, **kwargs: Any) -> 'State': + def __new__(cls, *args: Any, **kwargs: Any) -> State: """Return state type.""" kind = kwargs.get('type') if kind is None: @@ -221,7 +217,7 @@ def __init__( ) -> None: # TODO: should place the initial state here instead of onentry self.__type = kwargs.get('type', 'atomic') - self.__parent: Optional['SubstateMixin'] = None + self.__parent: Optional[SubstateMixin] = None self.name = name self.datamodel = kwargs.pop('datamodel', DataModel([])) self.datamodel.parent = self @@ -239,11 +235,11 @@ def __eq__(self, other: object) -> bool: def __repr__(self) -> str: return repr(f"{self.__class__.__name__}({self.name})") - def __iter__(self) -> 'State': + def __iter__(self) -> State: self.__stack = [self] return self - def __next__(self) -> 'State': + def __next__(self) -> State: # simple breadth-first iteration if self.__stack: x = self.__stack.pop() @@ -252,16 +248,16 @@ def __next__(self) -> 'State': return x raise StopIteration - def __reversed__(self) -> Generator['State', None, None]: - target: Optional['State'] = self + def __reversed__(self) -> Generator[State, None, None]: + target: Optional[State] = self while target: yield target target = target.parent @classmethod def create( - cls, settings: Union['State', dict, str] - ) -> Union['SubstateMixin', 'State']: + cls, settings: Union[State, dict, str] + ) -> Union[SubstateMixin, State]: """Create state from configuration.""" obj = None if isinstance(settings, State): @@ -311,7 +307,7 @@ def create( raise InvalidConfig('could not create state from provided settings') # @property - # def datamodel(self) -> 'DataModel': + # def datamodel(self) -> DataModel: # """Get datamodel data items.""" # return self.__datamodel @@ -321,26 +317,26 @@ def path(self) -> str: return '.'.join(reversed([x.name for x in reversed(self)])) @property - def type(self) -> 'str': + def type(self) -> str: """Get state type.""" return self.__type @property - def parent(self) -> Optional['SubstateMixin']: + def parent(self) -> Optional[SubstateMixin]: """Get parent state.""" return self.__parent @parent.setter - def parent(self, state: 'SubstateMixin') -> None: + def parent(self, state: SubstateMixin) -> None: if self.__parent is None: self.__parent = state else: raise SuperstateException('cannot change parent for state') - def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_entry(self, ctx: StateChart) -> Optional[Any]: """Run on-entry tasks.""" - def run_on_exit(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_exit(self, ctx: StateChart) -> Optional[Any]: """Run on-exit tasks.""" def validate(self) -> None: @@ -354,11 +350,11 @@ def validate(self) -> None: class PseudoState(State): """Provide state for statechart.""" - def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_entry(self, ctx: StateChart) -> Optional[Any]: """Run on-entry tasks.""" raise InvalidTransition('cannot transition to pseudostate') - def run_on_exit(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_exit(self, ctx: StateChart) -> Optional[Any]: """Run on-exit tasks.""" raise InvalidTransition('cannot transition from pseudostate') @@ -414,8 +410,8 @@ def __init__(self, name: str, **kwargs: Any) -> None: @classmethod def create( - cls, settings: Union['State', dict, str] - ) -> Union['SubstateMixin', 'State']: + cls, settings: Union[State, dict, str] + ) -> Union[SubstateMixin, State]: """Create state from configuration.""" obj = None if isinstance(settings, InitialState): @@ -438,7 +434,7 @@ def create( raise InvalidConfig('could not create state from provided settings') @property - def transition(self) -> 'Transition': + def transition(self) -> Transition: """Return transition of initial state.""" return self.transitions[0] @@ -474,11 +470,11 @@ def __init__(self, name: str, **kwargs: Any) -> None: self.on_entry = kwargs.pop('on_entry') super().__init__(name, **kwargs) - # def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: + # def run_on_entry(self, ctx: StateChart) -> Optional[Any]: # NOTE: SCXML Processor MUST generate the event done.state.id after # completion of the elements - def run_on_exit(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_exit(self, ctx: StateChart) -> Optional[Any]: raise InvalidTransition('final state cannot transition once entered') @@ -492,7 +488,7 @@ def __init__(self, name: str, **kwargs: Any) -> None: self.transitions = kwargs.pop('transitions', []) super().__init__(name, **kwargs) - def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_entry(self, ctx: StateChart) -> Optional[Any]: if self.datamodel.binding == 'late' and not hasattr( self.datamodel, 'maps' ): @@ -504,7 +500,7 @@ def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: class SubstateMixin(State): """Provide composite abstract to define nested state types.""" - __states: Dict[str, 'State'] = {} + __states: Dict[str, State] = {} def __getattr__(self, name: str) -> Any: if name.startswith('__'): @@ -515,12 +511,12 @@ def __getattr__(self, name: str) -> Any: raise AttributeError @property - def states(self) -> Dict[str, 'State']: + def states(self) -> Dict[str, State]: """Return states.""" return self.__states @states.setter - def states(self, states: List['State']) -> None: + def states(self, states: List[State]) -> None: """Define states.""" if not self.__states: self.__states = {} @@ -528,12 +524,12 @@ def states(self, states: List['State']) -> None: state.parent = self self.__states[state.name] = state - def add_state(self, state: 'State') -> None: + def add_state(self, state: State) -> None: """Add substate to this state.""" state.parent = self self.__states[state.name] = state - def get_state(self, name: str) -> Optional['State']: + def get_state(self, name: str) -> Optional[State]: """Get state by name.""" return self.states.get(name) @@ -545,7 +541,7 @@ def get_state(self, name: str) -> Optional['State']: # class SubstateMixin(State): # """Provide composite abstract to define nested state types.""" # -# __states: List['State'] = [] +# __states: List[State] = [] # # def __getattr__(self, name: str) -> Any: # if name.startswith('__'): @@ -556,23 +552,23 @@ def get_state(self, name: str) -> Optional['State']: # raise AttributeError # # @property -# def states(self) -> List['State']: +# def states(self) -> List[State]: # """Return states.""" # return self.__states # # @states.setter -# def states(self, states: List['State']) -> None: +# def states(self, states: List[State]) -> None: # """Define states.""" # if not self.__states: # for state in states: # self.add_state(state) # -# def add_state(self, state: 'State') -> None: +# def add_state(self, state: State) -> None: # """Add substate to this state.""" # state.parent = self # self.__states.append(state) # -# def get_state(self, name: str) -> Optional['State']: +# def get_state(self, name: str) -> Optional[State]: # """Get state by name.""" # for state in self.states: # if state.name == name: @@ -587,8 +583,8 @@ def get_state(self, name: str) -> Optional['State']: class CompoundState(SubstateMixin, AtomicState): """Provide nested state capabilitiy.""" - initial: 'Initial' - final: 'FinalState' + initial: Initial + final: FinalState def __init__(self, name: str, **kwargs: Any) -> None: # self.__current = self @@ -596,7 +592,7 @@ def __init__(self, name: str, **kwargs: Any) -> None: self.states = kwargs.pop('states', []) super().__init__(name, **kwargs) - def run_on_entry(self, ctx: 'StateChart') -> Optional[Tuple[Any, ...]]: + def run_on_entry(self, ctx: StateChart) -> Optional[Tuple[Any, ...]]: # if next( # (x for x in self.states if isinstance(x, HistoryState)), False # ): @@ -638,14 +634,14 @@ def __init__(self, name: str, **kwargs: Any) -> None: self.states = kwargs.pop('states', []) super().__init__(name, **kwargs) - def run_on_entry(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_entry(self, ctx: StateChart) -> Optional[Any]: results = [] results.append(super().run_on_entry(ctx)) for state in reversed(self.states.values()): results.append(state.run_on_entry(ctx)) return results - def run_on_exit(self, ctx: 'StateChart') -> Optional[Any]: + def run_on_exit(self, ctx: StateChart) -> Optional[Any]: results = [] for state in reversed(self.states.values()): results.append(state.run_on_exit(ctx)) diff --git a/src/superstate/transition.py b/src/superstate/transition.py index ff8ede7..183fc18 100644 --- a/src/superstate/transition.py +++ b/src/superstate/transition.py @@ -1,5 +1,7 @@ """Provide superstate transition capabilities.""" +from __future__ import annotations + import logging from typing import TYPE_CHECKING, Any, Callable, Optional, Union, cast @@ -31,7 +33,7 @@ class Transition: # __slots__ = ['event', 'target', 'action', 'cond', 'type'] - __source: Optional['State'] = None + __source: Optional[State] = None event: str = cast(str, Identifier(TRANSITION_PATTERN)) cond: Optional['ActionTypes'] target: str = cast(str, Identifier(TRANSITION_PATTERN)) @@ -56,7 +58,7 @@ def __repr__(self) -> str: return repr(f"Transition(event={self.event}, target={self.target})") def __call__( - self, ctx: 'StateChart', *args: Any, **kwargs: Any + self, ctx: StateChart, *args: Any, **kwargs: Any ) -> Optional[Any]: """Run transition process.""" # TODO: move change_state to process_transitions @@ -82,7 +84,7 @@ def __call__( return results @classmethod - def create(cls, settings: Union['Transition', dict]) -> 'Transition': + def create(cls, settings: Union[Transition, dict]) -> Transition: """Create transition from configuration.""" if isinstance(settings, Transition): return settings @@ -106,12 +108,12 @@ def create(cls, settings: Union['Transition', dict]) -> 'Transition': raise InvalidConfig('could not find a valid transition configuration') @property - def source(self) -> Optional['State']: + def source(self) -> Optional[State]: """Get source state.""" return self.__source @source.setter - def source(self, state: 'State') -> None: + def source(self, state: State) -> None: if self.__source is None: self.__source = state else: @@ -120,7 +122,7 @@ def source(self, state: 'State') -> None: def callback(self) -> Callable: """Provide callback from source state when transition is called.""" - def event(ctx: 'StateChart', *args: Any, **kwargs: Any) -> None: + def event(ctx: StateChart, *args: Any, **kwargs: Any) -> None: """Provide callback event.""" ctx.process_transitions(self.event, *args, **kwargs) @@ -128,7 +130,7 @@ def event(ctx: 'StateChart', *args: Any, **kwargs: Any) -> None: event.__doc__ = f"Transition event: '{self.event}'" return event - def evaluate(self, ctx: 'StateChart', *args: Any, **kwargs: Any) -> bool: + def evaluate(self, ctx: StateChart, *args: Any, **kwargs: Any) -> bool: """Evaluate conditionss of transition.""" result = True if self.cond: diff --git a/src/superstate/types.py b/src/superstate/types.py index 53906cf..a5a50c8 100644 --- a/src/superstate/types.py +++ b/src/superstate/types.py @@ -1,5 +1,7 @@ """Provide common types for statechart components.""" +from __future__ import annotations + import re from abc import ABC, abstractmethod # pylint: disable=no-name-in-module from collections.abc import Callable @@ -19,10 +21,10 @@ if TYPE_CHECKING: from superstate.model import Action -ExpressionType = Union[Callable, str] -ExpressionTypes = Sequence['ExpressionType'] -ActionTypes = Sequence['Action'] -Initial = Union[Callable, str] + ActionTypes = Sequence[Action] + ExpressionType = Union[Callable, str] + ExpressionTypes = Sequence[ExpressionType] + Initial = Union[Callable, str] T = TypeVar('T') @@ -78,7 +80,7 @@ def validate(self, value: str) -> None: class Expression(Validator): """Validate valueession.""" - def validate(self, value: 'ExpressionType') -> None: + def validate(self, value: ExpressionType) -> None: """Validate valueession.""" if isinstance(value, str): if '\n' in value: From e68a6359c44767c77178b1535f3b78442eef6e8f Mon Sep 17 00:00:00 2001 From: "Jesse P. Johnson" Date: Sun, 20 Apr 2025 19:09:56 +0200 Subject: [PATCH 2/2] ci(version): apply 1.6.2a1 updates --- pyproject.toml | 2 +- src/superstate/__init__.py | 2 +- tests/test_version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5445a4b..fc64b82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "superstate" -version = "1.6.2a0" +version = "1.6.2a1" description = "Robust statechart for configurable automation rules." readme = "README.md" license = {file = "LICENSE"} diff --git a/src/superstate/__init__.py b/src/superstate/__init__.py index 361d2ed..14d7b6e 100644 --- a/src/superstate/__init__.py +++ b/src/superstate/__init__.py @@ -64,7 +64,7 @@ __author_email__ = 'jpj6652@gmail.com' __title__ = 'superstate' __description__ = 'Compact statechart that can be vendored.' -__version__ = '1.6.2a0' +__version__ = '1.6.2a1' __license__ = 'MIT' __copyright__ = 'Copyright 2022 Jesse Johnson.' __all__ = ( diff --git a/tests/test_version.py b/tests/test_version.py index 6b0ef2b..76ec871 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -4,4 +4,4 @@ def test_version() -> None: """Test project metadata version.""" - assert __version__ == '1.6.2a0' + assert __version__ == '1.6.2a1'