Skip to content

fix(serde): round-trip tuples and sets in persisted state - #997

Open
RishavTiwari25 wants to merge 1 commit into
apache:mainfrom
RishavTiwari25:fix/serde-tuple-set
Open

RishavTiwari25 wants to merge 1 commit into
apache:mainfrom
RishavTiwari25:fix/serde-tuple-set

Conversation

@RishavTiwari25

Copy link
Copy Markdown

Tuples and sets in state did not survive persistence: they came back as strings.

persister = SQLLitePersister(db_path=":memory:", table_name="t"); persister.initialize()
state = State({"pair": (1, "a"), "tags": {"x"}, "nested": [(1, 2)]})
persister.save("pk", "app", 0, "a", state, "completed")
persister.load("pk", "app")["state"].get_all()
# {'pair': "(1, 'a')", 'tags': "{'x'}", 'nested': ['(1, 2)']}

serde.serialize registers handlers for str/int/float/bool, dict and list. tuple, set and frozenset fell through to the default implementation, which returns str(value), and nothing turns that string back. So an app that keeps e.g. (role, content) pairs or a set of seen ids in state gets strings after it is restored from a persister.

Changes

  • burr/core/serde.py: serialize handlers for tuple, set and frozenset that serialize each element (so nested values still go through serde) inside an envelope ({"__burr_serde__": "builtins.tuple" | "builtins.set" | "builtins.frozenset", "value": [...]}), plus the matching deserializers that rebuild the same type.
  • tests/core/test_serde.py: round-trip tests (through json.dumps/json.loads, as persisters store it) for tuples, empty tuples, sets, empty sets, frozensets, and tuples/sets nested in lists, dicts and sets, checking value and type; and a SQLLitePersister save/load test.

How I tested this

  • The new tests fail on main (8 of them) and pass with the change.
  • pytest tests/core tests/tracking tests/visibility tests/test_end_to_end.py tests/integrations/serde: 485 passed. The 6 failures there fail on main too on my machine (graphviz binary not installed; test_pickle compares pickle protocol bytes, which differ on Python 3.14); test_langchain.py was skipped for a missing dependency.
  • black (line length 100), isort and flake8 are clean on the changed files.

Notes

  • Values already persisted as strings stay strings; only new saves use the envelope.
  • The tracking UI will show a tuple/set state field as the envelope dict instead of the str() text, the same way it shows other serde-registered types.
  • A namedtuple is restored as a plain tuple (before, it was a string).

Checklist

  • PR has an informative and human-readable title (this will be pulled into the release notes)
  • Changes are limited to a single goal (no scope creep)
  • Code passed the pre-commit check & code is left cleaner/nicer than when first encountered.
  • Any change in functionality is tested
  • New functions are documented (with a description, list of inputs, and expected output)
  • Placeholder code is flagged / future TODOs are captured in comments
  • Project documentation has been updated if adding/changing functionality.

serialize() had no handler for tuple, set or frozenset, so they fell
through to the default str(value). A state value (1, 'a') was persisted
as the string "(1, 'a')" and loaded back as that string, and the same
happened to sets and to tuples nested in lists or dicts.

They are now serialized element by element in a serde envelope and
deserialized back to the same type.
@github-actions github-actions Bot added the area/core Application, State, Graph, Actions label Oct 26, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Application, State, Graph, Actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant