You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
added the concept of an explicit error state which is transitioned to after any unhandled transitions, in order to allow for explicit recoverability after raising an exception by specifying a custom one.
The benefits of this approach:
first and foremost, you can structurally tell whether the state- or input-based data you require has been initialized without looking at the state transition graph; "does this attribute exist on the state class you're trying to implement" is a much simpler question to answer
no need for special "feedback" handling
it's much easier to categorize and hide state-machine implementation details, as only the explicit Protocol is exposed to callers, and state-specific classes may have whatever internal methods they require; no need for lots of _actually methods
in particular: state objects are actually useful now and don't produce a useless / misleading / not-actually-callable def in the middle of your class body; it's a class, that can be used as a regular class if you want (for easier testing, etc)
dependency injection and state core easily resolve both forms of inter-state dependencies'
everything is a decorator now so the API is more self-consistent; no need for lots of awkward class-scoped code execution
magical pseudo-method behavior now lives on a synthetic class that does not share an implementation namespace with you, except for the explicit Protocol methods that you declare.
Open questions:
I don't love the @handle / @implement decorator naming; wondering if I should just use upon
should @implement-ed methods be treated more like just a convenient way to declare default fallback methods that exist on every state? hmm probably, right now the precedence behavior is an accident, and it fails silently if you do both, so it should either do this or fail noisily
what's a better name for TypicalBuilder?
Should TypicalClass exist at all, or could we just give _realSyntheticType some kind of constructor that does all the work it's currently doing? (If we do this, how do we communicate the custom constructor signature?)
Here's a taste of what it looks like to use:
from __future__ importannotationsfromdataclassesimportdataclassfromtypingimportProtocol, AnnotatedasAfromautomatimportTypicalBuilder, EnterclassCoffeeMachine(Protocol):
defput_in_beans(self, beans: str) ->None:
"put in some beans"defbrew_button(self) ->None:
"press the brew button"@dataclassclassBrewerStateCore(object):
heat: int=0coffee=TypicalBuilder(CoffeeMachine, BrewerStateCore)
@coffee.state()classNoBeanHaver(object):
@coffee.handle(CoffeeMachine.brew_button)defno_beans(self) ->None:
print("no beans, not heating")
@coffee.handle(CoffeeMachine.put_in_beans)defadd_beans(self, beans) ->A[None, Enter(BeanHaver)]:
print("put in some beans", repr(beans))
@coffee.state(persist=False)@dataclassclassBeanHaver:
core: BrewerStateCorebeans: str@coffee.handle(CoffeeMachine.brew_button)defheat_the_heating_element(self) ->A[None, Enter(NoBeanHaver)]:
self.core.heat+=1print("yay brewing:", repr(self.beans))
@coffee.handle(CoffeeMachine.put_in_beans)deftoo_many_beans(self, beans: object) ->None:
print("beans overflowing:", repr(beans), self.beans)
CoffeeStateMachine=coffee.buildClass()
print("Created:", CoffeeStateMachine)
x: CoffeeMachine=CoffeeStateMachine(3)
print(isinstance(x, CoffeeStateMachine))
x.brew_button()
x.brew_button()
x.put_in_beans("old beans")
x.put_in_beans("oops too many beans")
x.brew_button()
x.brew_button()
x.put_in_beans("new beans")
x.brew_button()
The text was updated successfully, but these errors were encountered:
glyph
changed the title
automat2: type-hints based API with separate
automat2: type-hints based API with separate instance objects per state
Jun 13, 2022
glyph
changed the title
automat2: type-hints based API with separate instance objects per state
automat2: type-hints based API with a separate class per state
Jun 13, 2022
_magicValueForParameter might be a tag too magical. For example, what if beans has to be lower-cased? NoBeanHaver.add_beans would love to lower-case the beans, but it can't say "send this along to the state I'm entering".
Half-follow-up: I'm not sure the "Core" stuff is all that useful? If there was an explicit way to say "send these parameters to the next state", then relevant methods could send a "common" dataclass voluntarily.
How does the initial state specified? Is it just the first decorated state? (This feels a bit too magical)
Silly question: the heat only goes up, each time the beans are brewed. As the sole parameter of the Core class, I was trying to understand what it stands for, and wasn't sure because of that issue 🙂
Inspired by several issues, including #41 #127 #130 #116 #112 #116, as well as developments in modern Python syntax and tooling (i.e. annotations and mypy) I started noodling around in the
typical
branch to try to come up with a new interface.The general gist is:
typing.Protocol
to describe your inputs.The benefits of this approach:
_actually
methodsdef
in the middle of your class body; it's a class, that can be used as a regular class if you want (for easier testing, etc)Open questions:
@handle
/@implement
decorator naming; wondering if I should just useupon
@implement
-ed methods be treated more like just a convenient way to declare default fallback methods that exist on every state? hmm probably, right now the precedence behavior is an accident, and it fails silently if you do both, so it should either do this or fail noisilyTypicalBuilder
?TypicalClass
exist at all, or could we just give_realSyntheticType
some kind of constructor that does all the work it's currently doing? (If we do this, how do we communicate the custom constructor signature?)Here's a taste of what it looks like to use:
The text was updated successfully, but these errors were encountered: