Skip to content

Release v0.26.2#864

Merged
thomashebrard merged 3 commits into
mainfrom
release/v0.26.2
May 6, 2026
Merged

Release v0.26.2#864
thomashebrard merged 3 commits into
mainfrom
release/v0.26.2

Conversation

@thomashebrard
Copy link
Copy Markdown
Member

@thomashebrard thomashebrard commented May 6, 2026

Release v0.26.2

Bumps version from 0.26.1 to 0.26.2.

Changelog

[v0.26.2] - 2026-05-06

Fixed

  • choices fields no longer fail validation with 'EnumName.MEMBER_NAME' errors. A concept declared with choices = [...] produces a Literal[...] field on the dynamic Pydantic class. That schema is round-tripped through SchemaToModelFactory.make_from_json_schema (used to rebuild dynamic models on Temporal workers and to feed structured-output schemas to LLM providers). Previously the round-trip silently re-emitted the field as a plain Python Enum class — e.g. Literal["Strong Match", "Good Match", "Partial Match", "Poor Match"] became class Recommendation(Enum): Poor_Match = "Poor Match"; .... LLMs filling that schema then returned the enum's Python repr ("Recommendation.Poor_Match") instead of the literal string ("Poor Match"), which failed Pydantic validation against the original choice set with errors like Invalid choice errors: 'recommendation': got 'Recommendation.Poor_Match', expected one of 'Strong Match', 'Good Match', 'Partial Match' or 'Poor Match'. _generate_source_from_schema now passes enum_field_as_literal=LiteralType.All to datamodel-code-generator, so enum: [strings] schema nodes round-trip as Literal[...] instead of being regenerated as Enum classes. _exec_source_to_types now also exposes Literal in the rebuild namespace so model_rebuild resolves the deferred annotations.

Summary by cubic

Ensures choices fields stay Literal[...] when rebuilding models from JSON Schema and supports RootModel[Literal[...]] shapes to prevent 'EnumName.MEMBER_NAME' validation errors. Releases v0.26.2.

  • Bug Fixes
    • Use enum_field_as_literal=LiteralType.All with datamodel-code-generator so enum: [strings] round-trip as Literal[...] (not generated Enum) and serialize as plain strings.
    • Resolve rebuild-time annotations by exposing Literal and skipping base RootModel when collecting types, ensuring dynamic models rebuild and round-trip correctly.

Written for commit 974b3b3. Summary will update on new commits.

thomashebrard and others added 2 commits May 6, 2026 13:15
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 7 files

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 6, 2026

Greptile Summary

Fixes a Literal→Enum regression in SchemaToModelFactory where choices = [...] fields were silently re-emitted as Python Enum classes during JSON-schema round-trips, causing LLMs to return "Recommendation.Poor_Match" instead of "Poor Match" and fail Pydantic validation.

  • Core fix (schema_to_model_factory.py): passes enum_field_as_literal=LiteralType.All to datamodel-code-generator so enum: [strings] nodes stay as Literal[...]; also injects Literal into rebuild_namespace so model_rebuild can resolve deferred annotations.
  • Side-effect: $defs-based enums now round-trip as RootModel[Literal[...]] instead of Enum subclasses; integration test updated to assert on model_dump() equality rather than Enum instance introspection.
  • New unit test adds a regression guard that checks both the generated source text and the resolved field annotation to prevent re-introduction of the bug.

Confidence Score: 3/5

The Literal-choices fix is correct and well-tested, but model_rebuild is now called on Pydantic's own RootModel base class on every schema with a $defs-based enum field.

The core enum_field_as_literal=LiteralType.All change solves the reported validation bug cleanly and the tests cover the happy path, but the unguarded model_rebuild call on Pydantic's own RootModel base class is a latent fragility on every schema with a $defs-based enum field.

pipelex/cogt/content_generation/schema_to_model_factory.py — specifically the all_user_types filter in _exec_source_to_types and the model_rebuild loop that follows it.

Important Files Changed

Filename Overview
pipelex/cogt/content_generation/schema_to_model_factory.py Core fix: adds enum_field_as_literal=LiteralType.All to code-gen and injects Literal into rebuild_namespace. New issue: RootModel (the Pydantic base class) is now imported by generated source and can slip into all_user_types, causing model_rebuild to be called on Pydantic's own generic base class.
tests/unit/pipelex/cogt/content_generation/test_schema_to_model.py New regression test verifies that a Literal[...] choices field round-trips as Literal (not Enum) through make_from_json_schema; assertions cover both the generated source text and the resolved field annotation.
tests/integration/pipelex/temporal/data_converter/test_data_conv_enum_roundtrip.py Test updated to reflect that $defs-based enums now round-trip as RootModel[Literal[...]]; assertion shifted from Enum-instance introspection to model_dump() equality, which tests the contract that matters at the Temporal payload boundary.
CHANGELOG.md v0.26.2 entry added; two stale indentation blocks from earlier entries also cleaned up.
pyproject.toml Version bump from 0.26.1 to 0.26.2.

Sequence Diagram

sequenceDiagram
    participant Caller
    participant SchemaToModelFactory
    participant datamodel_codegen as datamodel-code-generator
    participant exec_ns as exec namespace
    participant Pydantic

    Caller->>SchemaToModelFactory: make_from_json_schema(schema, class_name)
    SchemaToModelFactory->>SchemaToModelFactory: _reject_unsafe_schema_extensions(schema)
    SchemaToModelFactory->>datamodel_codegen: generate(schema, enum_field_as_literal=LiteralType.All)
    Note over datamodel_codegen: enum strings stay as Literal
    datamodel_codegen-->>SchemaToModelFactory: source_code (Python str)
    SchemaToModelFactory->>exec_ns: exec(source_code, restricted_builtins)
    exec_ns-->>SchemaToModelFactory: namespace with user types and RootModel base class
    SchemaToModelFactory->>SchemaToModelFactory: filter all_user_types
    SchemaToModelFactory->>Pydantic: model_rebuild each BaseModel with Literal in namespace
    Pydantic-->>SchemaToModelFactory: resolved annotations
    SchemaToModelFactory-->>Caller: reconstructed class with kajson_class_source
Loading

Comments Outside Diff (1)

  1. pipelex/cogt/content_generation/schema_to_model_factory.py, line 256-264 (link)

    P1 With enum_field_as_literal=LiteralType.All, generated source now includes from pydantic import BaseModel, RootModel. After exec(), Pydantic's own RootModel base class enters namespace and passes the existing filter (only BaseModel itself is excluded). This causes model_rebuild to be called on Pydantic's unparameterized generic base class with a namespace that doesn't contain its internal RootModelRootType type variable. Adding the same is not guard that already exists for BaseModel and Enum prevents Pydantic internals from being touched.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: pipelex/cogt/content_generation/schema_to_model_factory.py
    Line: 256-264
    
    Comment:
    With `enum_field_as_literal=LiteralType.All`, generated source now includes `from pydantic import BaseModel, RootModel`. After `exec()`, Pydantic's own `RootModel` base class enters `namespace` and passes the existing filter (only `BaseModel` itself is excluded). This causes `model_rebuild` to be called on Pydantic's unparameterized generic base class with a namespace that doesn't contain its internal `RootModelRootType` type variable. Adding the same `is not` guard that already exists for `BaseModel` and `Enum` prevents Pydantic internals from being touched.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
pipelex/cogt/content_generation/schema_to_model_factory.py:256-264
With `enum_field_as_literal=LiteralType.All`, generated source now includes `from pydantic import BaseModel, RootModel`. After `exec()`, Pydantic's own `RootModel` base class enters `namespace` and passes the existing filter (only `BaseModel` itself is excluded). This causes `model_rebuild` to be called on Pydantic's unparameterized generic base class with a namespace that doesn't contain its internal `RootModelRootType` type variable. Adding the same `is not` guard that already exists for `BaseModel` and `Enum` prevents Pydantic internals from being touched.

```suggestion
        from pydantic import RootModel  # noqa: PLC0415

        all_user_types: dict[str, type[Any]] = {
            name: obj
            for name, obj in namespace.items()
            if isinstance(obj, type)
            and not name.startswith("_")
            and (issubclass(obj, BaseModel) or issubclass(obj, Enum))
            and obj is not BaseModel
            and obj is not Enum
            and obj is not RootModel
        }
```

Reviews (1): Last reviewed commit: "Release v0.26.2" | Re-trigger Greptile

lchoquel
lchoquel previously approved these changes May 6, 2026
@thomashebrard thomashebrard merged commit 09a7f5c into main May 6, 2026
30 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 6, 2026
@thomashebrard thomashebrard deleted the release/v0.26.2 branch May 11, 2026 16:40
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants