-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
State typing #139
Comments
Adding to the discussion, I think the "pydantic" and the "decorator/class" approaches could be dubbed "centralized" vs "decentralized" state model. I'll be focusing on the benefits of "centralized" state model, which could be slightly different than the above "typed state" benefits. The simplest integration would be to subclass from pydantic import BaseModel
from burr.core import State
class BurrState(BaseModel, State):
foo: int
bar: str Then, the model is passed to the app = (
ApplicationBuilder()
.with_actions(...)
.with_transition(...)
.with_state(model=BurrState())
.with_entrypoint(...)
.build() Graph structure validationWe can ensure that all Default stateInstead of passing values field-by-field to the Data validationPydantic has many validation functions including validate_assignment which could trigger validation of specific fields on IntegrationsMany LLM tools leverage Pydantic. For instance, a |
Good overview. Some other considerations:
|
OK, API decision, this is up next on implementation. Will support a few different ways to do it -- key is that it all compiles. We support centralized and decentralized. Inputs are typed as normal. Defining state typesAs long as we have a spec of types, it's pretty easy: # stdlib
OverallState = TypedState[{"a": int, "b": int, "c": int, "d": int}]
OverallState = TypedState[ABCDDataclass]
# with pydantic plugin
OverallState = TypedState[ABCDPydanticModel] Defining actionsThen we can use: @action(reads=["a", "b"], writes=["c", "d"])
def foo(state: OverallState) -> OverallState:
pass Note you can also define this anonymously. Probably going to require the reads/writes still, but if you think about it it's technically optional... @action(reads=["a", "b"], writes=["c", "d"])
def foo(state: State[{"a": int, "b": int}]) -> State[{"c": int, "d": int}]:
pass integrating into app -- optional:graph = GraphBuilder()....with_typing(TypedState) # or on the application builder Note this will work with or without the above -- more likely one would do the other burr.typing.get_type_dict(graph)
burr.typing.get_action_input_dict(graph, action)
burr.typing.get_action_state_input_dict(graph, action)
burr.typing.get_action_state_output_dict(graph, action) b_pydantic.get_type_model(graph, exclude=..., include=...)
b_pydantic.get_action_input_model(graph, action)
b_pydantic.get_action_state_input_model(graph, action)
b_pydantic.get_action_state_output_model(graph, action) |
MyState = PydanticState[MyModel]
class MyState:
substate:
@action(reads=["a", "b"], writes=["a", "b"])
def my_action(state: MyState) -> MyState:
state.c = fn(state.a, state.b)
state.d =...
return state
@action(reads=["a", "b"], writes=["c", "d"])
def my_action(state: MyState) -> MyState:
state.model.c = fn(state.a, state.b)
state.model.d =...
return state
class PydanticState:
a: Optional[AModel]
b: Optional[BModel]
@action(reads=["a", "b"], writes=["c", "d"])
# the state model is the dynamically subsetted one
def my_action(state: PydanticState) -> PydanticState:
state.model.c = fn(state.a, state.b)
state.model.d = ...
state.model.e # throw an error (not allowed to read from e, doesn't exist/not declared)
state.model.a = "foo" # throw an error, because you didn't declare it
state.model.d =...
return state
@action(reads=["*"], writes=[...])
# subset everything?
def my_action(state: PydanticState) -> PydanticState:
# do the kitchen sink -- whatever you want with the whole state
return state ...
## Idea -- give everything state
@action(reads="*", writes=["a", "b"])
def my_action(state: MyState) -> MyState:
state.c = fn(state.a, state.b)
state.d =...
return state
Application[MyState]
builder.with_state(MyState).build()
application.state # IDE should know MyState
state, ... = application.run(...) #IDE should konw MyState |
See #350 |
This has qwuite a few pieces to it: 1. Adds centralized state with a typing system 2. Adds decentralized state 3. Adds pydantic implementations See issue #139 for more details on design decisions
This has qwuite a few pieces to it: 1. Adds centralized state with a typing system 2. Adds decentralized state 3. Adds pydantic implementations See issue #139 for more details on design decisions
closing since #350 was merged |
Currently state is untyped. Ideally this should be able to leverage a pydantic model, and possibly a typed dict/whatnot. We'll need the proper abstractions however.
Some requirements:
writes
/reads
potentially). Then we have different actions that can be compiled together. We should also be able to do this centrally.Any
, which is bidirectionally compatible with typing.Ideas:
pydantic
-
hard to do transactional updates+
IDE integration is easy+
easy integration with web-services/fastAPI~
subsetting is a bit of work, but we can bypass that by using the whole statein the decorator/class
-
No free IDE integration (without a plugin)+
simple, loosely coupled, easy to inspect~
duplicated between readers and writers (can't decide if this is bad?)The text was updated successfully, but these errors were encountered: