Skip to content

_create_single_variant emits empty BaseModel union for array-root schemas (totals) #37

@pjordan

Description

@pjordan

Summary

The generated request variants for array-root schemas contain a spurious empty BaseModel unioned with the list type. Affected files:

  • src/ucp_sdk/models/schemas/shopping/types/totals_create_request.py
  • src/ucp_sdk/models/schemas/shopping/types/totals_update_request.py

Each defines a *Request1 class that is an empty BaseModel (just model_config = ConfigDict(extra="allow")), and the top-level RootModel unions a list with this empty model. Flagged by Gemini on #28:

Root cause

ucp/source/schemas/shopping/types/totals.json is a clean root-level array schema ("type": "array", with items). The bug lives in preprocess_schemas.py::_create_single_variant (lines ~357-384), which unconditionally writes:

new_props = {}
new_required = []
for name, data in schema.get("properties", {}).items():   # empty for an array root
    ...
variant["properties"] = new_props          # injects empty {}
variant["required"] = new_required         # injects []

So the variant ends up with "type": "array", "items": {...}, plus "properties": {} and "required": []. datamodel-codegen interprets this as a schema satisfying both an array form and an object form, and emits RootModel[list[Item] | EmptyBaseModel] - exactly the *Request1 artifact Gemini flagged.

Secondary bug

Variant creation never recurses into items, so the nested ucp_request: "omit" marker on lines is silently lost - TotalsCreateRequestItem / TotalsUpdateRequestItem are functionally identical to Total, defeating the purpose of the variant.

Proposed fix

Patch _create_single_variant in preprocess_schemas.py:

  1. Guard the object-shape mutation. Only set variant["properties"] / variant["required"] when the schema is object-shaped (type == "object" or already has properties). For array roots, leave them absent.
  2. Recurse into items. When type == "array" and items is an object schema, run the same ucp_request filtering on items.properties (and on each allOf branch's properties, since totals uses allOf inside items).

Sketch:

def _apply_ucp_request_to_object(obj, op, file_path, variant_needs):
    # filter properties + required in-place, pop ucp_request, rewrite refs
    ...

def _create_single_variant(schema, op, stem, file_path, variant_needs):
    variant = copy.deepcopy(schema)
    update_variant_identity(variant, op, stem)
    rewrite_refs_to_variants(variant, op, file_path, variant_needs)

    if variant.get("type") == "array" and isinstance(variant.get("items"), dict):
        for node in iter_nodes(variant["items"]):
            if isinstance(node, dict) and "properties" in node:
                _apply_ucp_request_to_object(node, op, ...)
    elif "properties" in variant or variant.get("type") == "object":
        _apply_ucp_request_to_object(variant, op, ...)
    return variant

This is a pipeline bug - the source schema is fine; post-processing the generated .py would be brittle.

Verification

After fix, regenerate and confirm:

  • No class TotalsCreateRequest1 / TotalsUpdateRequest1 in the output.
  • TotalsCreateRequestItem / TotalsUpdateRequestItem reflect the ucp_request: "omit" filtering on nested lines.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions