Skip to content

fix(types): _DictLike mixin for response Structs (v0.4.3)#9

Merged
vitalii-dynamiq merged 1 commit into
mainfrom
fix/struct-dict-access
May 6, 2026
Merged

fix(types): _DictLike mixin for response Structs (v0.4.3)#9
vitalii-dynamiq merged 1 commit into
mainfrom
fix/struct-dict-access

Conversation

@vitalii-dynamiq

@vitalii-dynamiq vitalii-dynamiq commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Patch release. End-to-end validation in dynamiq's unit-test suite surfaced the litellm-fixture-compat gap: tests assemble response objects with dict-style assignment (`response["data"] = [...]`), a pattern that worked on litellm's Pydantic-derived models but not on arcllm's strongly-typed `msgspec.Struct` records.

Adds a `_DictLike` mixin defined once and applied to all 17 public Struct types in `arcllm.types`:

Pattern Resolves to
`response[key]` `getattr(response, key)` (raises KeyError if absent)
`response[key] = value` `setattr(...)` — no type validation (litellm-style loose typing escape hatch)
`key in response` field exists and is non-None
`response.get(key, default)` safe attribute lookup with default

Real provider code keeps the canonical attribute path with full msgspec type-checking. The loose surface is for tests and ad-hoc mutation.

Validation

  • arcllm own suite — 776 pass (5 new `TestDictLikeAccess` regression tests)
  • mypy --strict + ruff + format — clean
  • After release: 10 dynamiq embedder fixture tests should pass (was failing on `EmbeddingResponse["data"] = ...`)

Bumps to 0.4.3.


Note

Medium Risk
Moderate risk because it changes behavior of all public msgspec.Struct response types by allowing post-construction mutation via __setitem__ without validation, which could mask type issues or alter downstream expectations.

Overview
Adds a _DictLike mixin to make all public response msgspec.Struct types support LiteLLM-style dict semantics (obj[key], obj[key]=..., key in obj, obj.get(...)) while preserving existing attribute access.

Bumps package version to 0.4.3 and adds regression tests ensuring item access works, unknown keys raise KeyError, and __contains__ reflects non-None fields.

Reviewed by Cursor Bugbot for commit 38a7ad5. Bugbot is set up for automated code reviews on this repo. Configure here.

End-to-end validation in dynamiq's unit-test suite surfaced the second
half of the litellm-fixture-compat gap: tests assemble response objects
with dict-style assignment (``response["data"] = [...]``) — a pattern
that worked on litellm's Pydantic-derived models but not on arcllm's
strongly-typed ``msgspec.Struct`` records.

Adds a ``_DictLike`` mixin (defined once, applied to all 17 public
Struct types in ``arcllm.types``) that forwards:

- ``response[key]`` → ``getattr(response, key)`` (raises KeyError if absent)
- ``response[key] = value`` → ``setattr(response, key, value)`` (no
  validation — fixtures can store dicts where the field is typed as
  a Struct list, mirroring litellm's loose typing)
- ``key in response`` → field exists AND is non-None
- ``response.get(key, default)`` → safe attribute lookup with default

Real provider code keeps the canonical attribute path with full
msgspec type-checking — the loose escape hatch is for callers (tests
and hot-paths that need quick mutation).

Verified the embedder test path that read ``response.data[0]["embedding"]``
also works: ``data[0]`` is now whatever the fixture stored (typically
a plain dict), and dict indexing on the dict is native Python.

Bumps to 0.4.3.
@vitalii-dynamiq vitalii-dynamiq merged commit 6f3333d into main May 6, 2026
15 checks passed
@vitalii-dynamiq vitalii-dynamiq deleted the fix/struct-dict-access branch May 6, 2026 17:06

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 38a7ad5. Configure here.

Comment thread arcllm/types.py
return hasattr(self, key) and getattr(self, key) is not None

def get(self, key: str, default: Any = None) -> Any:
return getattr(self, key, default)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get returns None when __contains__ reports key absent

Medium Severity

The get method and __contains__ have inconsistent semantics for None-valued struct fields. __contains__ treats a field with value None as absent (returns False), but get uses bare getattr which finds the attribute and returns None rather than the caller's default. This breaks the standard dict contract: if "usage" not in response is True, callers expect response.get("usage", fallback) to return fallback, but it returns None instead.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 38a7ad5. Configure here.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant