[PR #15] [MERGED] Add a typed-context API #48

Closed
opened 2026-02-16 02:16:23 -05:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/run-llama/workflows-py/pull/15
Author: @logan-markewich
Created: 6/21/2025
Status: Merged
Merged: 6/25/2025
Merged by: @logan-markewich

Base: mainHead: logan/typed_workflow_state


📝 Commits (9)

📊 Changes

9 files changed (+587 additions, -51 deletions)

View changed files

📝 src/workflows/context/context.py (+102 -43)
📝 src/workflows/context/serializers.py (+1 -1)
src/workflows/context/state_store.py (+258 -0)
📝 src/workflows/decorators.py (+3 -1)
📝 src/workflows/utils.py (+20 -2)
📝 tests/test_checkpointer.py (+2 -1)
📝 tests/test_context.py (+4 -3)
tests/test_state_manager.py (+157 -0)
tests/test_workflow_typed_state.py (+40 -0)

📄 Description

This PR adds an opt-in typed-context state. This effectively replaces the old get/set API

By default, the state is effectively an untyped dict (taking advantage of the base Event class features). This means users will see no breaking change in core usage, and the existing serdes flow continues to work.

However, users can also opt-in to a typed state experience.

from pydantic import BaseModel, Field

from workflows import Context, Workflow
from workflows.decorators import step
from workflows.events import StartEvent, StopEvent

# State must have defaults for every field
class MyState(BaseModel):
    name: str = Field(default="Jane")
    age: int = Field(default=25)


class MyWorkflow(Workflow):
    @step
    async def step(self, ctx: Context[MyState], ev: StartEvent) -> StopEvent:
        # Modify state attributes
        await ctx.state.set("name", "John")
        await ctx.state.set("age", 30)

        # Get and update entire state
        state = await ctx.state.get_all()
        state.age += 1
        await ctx.state.set_all(state)

        return StopEvent()


async def main():
    wf = MyWorkflow()

    handler = wf.run()

    # Run the workflow
    _ = await handler

    # Check final state
    state = await handler.ctx.state.get_all()
    assert state.model_dump() == MyState(name="John", age=31).model_dump()

Design:

  • Async first
  • Non-breaking
  • Designed to (hopefully) be DB ready at some point in the future if we want remote context

Limitations:

  • Only one "state" type is supported per workflow (although we could easily support more than one)
  • We might want a run_id or context_id in the future to support remote-storage easily.
  • ctx.clear might need to be deprecated because its not async

Deprecations/Breaking Changes

  • Removed ctx.data, ctx._serialize_globals, and ctx._globals
  • Deprecated ctx.get() and ctx.set() in favor of ctx.state.get() and ctx.state.set()

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/run-llama/workflows-py/pull/15 **Author:** [@logan-markewich](https://github.com/logan-markewich) **Created:** 6/21/2025 **Status:** ✅ Merged **Merged:** 6/25/2025 **Merged by:** [@logan-markewich](https://github.com/logan-markewich) **Base:** `main` ← **Head:** `logan/typed_workflow_state` --- ### 📝 Commits (9) - [`f08a9fe`](https://github.com/run-llama/workflows-py/commit/f08a9fec4c9c0f5d59d0539d19a3fe3c35b8bd56) wip - [`2f8c24d`](https://github.com/run-llama/workflows-py/commit/2f8c24d4ea29a6eb04f2b5ec3d020fe9cafabdb6) wip - [`ac8737f`](https://github.com/run-llama/workflows-py/commit/ac8737fb0169d494b077193467cba076f4c15c58) fix all tests - [`81c743c`](https://github.com/run-llama/workflows-py/commit/81c743c0476b1e3763e42477c7524409c7d042c2) more tests - [`1c30e5c`](https://github.com/run-llama/workflows-py/commit/1c30e5ce0125d2d849bedcf800bd775ef416c914) make clear make sense - [`192d9b3`](https://github.com/run-llama/workflows-py/commit/192d9b337e4ed764cfb8425c058fe11ca12ed0aa) linting - [`49621fb`](https://github.com/run-llama/workflows-py/commit/49621fb218c02bf0624b4cb2c8b8da0be9a32a71) more typing - [`3418ae9`](https://github.com/run-llama/workflows-py/commit/3418ae9e74dcdc3139da9dc4b2c5032e4c612df1) rename store -> store - [`330816e`](https://github.com/run-llama/workflows-py/commit/330816e372ae6d946fd4aa2187c3589c25434ab2) get/set_all -> get/set_state ### 📊 Changes **9 files changed** (+587 additions, -51 deletions) <details> <summary>View changed files</summary> 📝 `src/workflows/context/context.py` (+102 -43) 📝 `src/workflows/context/serializers.py` (+1 -1) ➕ `src/workflows/context/state_store.py` (+258 -0) 📝 `src/workflows/decorators.py` (+3 -1) 📝 `src/workflows/utils.py` (+20 -2) 📝 `tests/test_checkpointer.py` (+2 -1) 📝 `tests/test_context.py` (+4 -3) ➕ `tests/test_state_manager.py` (+157 -0) ➕ `tests/test_workflow_typed_state.py` (+40 -0) </details> ### 📄 Description This PR adds an opt-in typed-context state. This effectively replaces the old get/set API By default, the state is effectively an untyped dict (taking advantage of the base `Event` class features). This means users will see no breaking change in core usage, and the existing serdes flow continues to work. However, users can also opt-in to a typed state experience. ```python from pydantic import BaseModel, Field from workflows import Context, Workflow from workflows.decorators import step from workflows.events import StartEvent, StopEvent # State must have defaults for every field class MyState(BaseModel): name: str = Field(default="Jane") age: int = Field(default=25) class MyWorkflow(Workflow): @step async def step(self, ctx: Context[MyState], ev: StartEvent) -> StopEvent: # Modify state attributes await ctx.state.set("name", "John") await ctx.state.set("age", 30) # Get and update entire state state = await ctx.state.get_all() state.age += 1 await ctx.state.set_all(state) return StopEvent() async def main(): wf = MyWorkflow() handler = wf.run() # Run the workflow _ = await handler # Check final state state = await handler.ctx.state.get_all() assert state.model_dump() == MyState(name="John", age=31).model_dump() ``` Design: - Async first - Non-breaking - Designed to (hopefully) be DB ready at some point in the future if we want remote context Limitations: - Only one "state" type is supported per workflow (although we could easily support more than one) - We might want a run_id or context_id in the future to support remote-storage easily. - `ctx.clear` might need to be deprecated because its not async Deprecations/Breaking Changes - Removed `ctx.data`, `ctx._serialize_globals`, and `ctx._globals` - Deprecated `ctx.get()` and `ctx.set()` in favor of `ctx.state.get()` and `ctx.state.set()` --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-02-16 02:16:23 -05:00
yindo closed this issue 2026-02-16 02:16:23 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: run-llama/workflows-py#48