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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions