Skip to content

Preserve tuple item types in Python SDKs - #44

Closed
glenn-jocher wants to merge 1 commit into
mainfrom
codex/python-prefix-items
Closed

Preserve tuple item types in Python SDKs#44
glenn-jocher wants to merge 1 commit into
mainfrom
codex/python-prefix-items

Conversation

@glenn-jocher

@glenn-jocher glenn-jocher commented Aug 19, 2026

Copy link
Copy Markdown
Member

Summary

  • derive Python array element types from OpenAPI prefixItems when items is absent
  • preserve runtime-correct list[...] output while deduplicating homogeneous tuple item types
  • add one focused response-model regression case

Why

SDK PR ultralytics/sdk#28 exposes valid tuple schemas for image and depth shapes. The Python renderer currently ignores prefixItems, degrading those fields from numeric lists to list[Any]. This fixes the generic renderer owner instead of patching generated SDK files.

Validation

  • bun run lint
  • bun run typecheck
  • bun run knip
  • bun run test (19 passed, 280 assertions)
  • regenerated the live 80-operation Platform SDK: only types.py changes, reducing list[Any] occurrences from 40 to 25
  • Ruff, Python compile, package build, and all 4 SDK consumer tests pass on the regenerated output

🛠️ PR Summary

Made with ❤️ by Ultralytics Actions

🌟 Summary

Updated the Python SDK generator to derive array element types from OpenAPI prefixItems when items is absent, preserving specific tuple-derived list types instead of generating list[Any].

📊 Key Changes

  • Added shared array item-type resolution that:
    • Uses items when present.
    • Falls back to prefixItems for tuple schemas.
    • Deduplicates repeated item types and combines distinct types.
    • Defaults to Any when no item types are available.
  • Updated both general Python type generation and response-model generation to use the new array handling.
  • Extended the OpenAPI example with a numeric two-item shape tuple schema.
  • Added a regression assertion verifying that shape generates as NotRequired[list[float]].

🎯 Purpose & Impact

Python SDKs now preserve usable element types for array fields defined with OpenAPI prefixItems, reducing unnecessary list[Any] output while keeping homogeneous tuple items represented as a single list type.

@UltralyticsAssistant UltralyticsAssistant added bug Something isn't working fixed Bug has been resolved labels Aug 19, 2026
@UltralyticsAssistant

Copy link
Copy Markdown
Member

👋 Hello @glenn-jocher, thank you for submitting a ultralytics/openapi 🚀 PR! This automated message confirms your contribution was received, and an Ultralytics engineer will assist with the review. To ensure a seamless integration of your work, please review the following checklist:

  • Define a Purpose: Clearly explain the purpose of your fix or feature in your PR description, and link to any relevant issues. Ensure your commit messages are clear, concise, and adhere to the project's conventions.
  • Synchronize with Source: Confirm your PR is synchronized with the ultralytics/openapi main branch. If it's behind, update it by clicking the 'Update branch' button or by running git pull and git merge main locally.
  • Ensure CI Checks Pass: Verify all Ultralytics Continuous Integration (CI) checks are passing. If any checks fail, please address the issues.
  • Update Documentation: Update the relevant documentation for any new or modified features.
  • Add Tests: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
  • Sign the CLA: Please ensure you have signed our Contributor License Agreement if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
  • Minimize Changes: Limit your changes to the minimum necessary for your bug fix or feature addition. "It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is." — Bruce Lee

For more guidance, please refer to our Contributing Guide. Don't hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀

@UltralyticsAssistant UltralyticsAssistant left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🔍 PR Review

Made with ❤️ by Ultralytics Actions

Reviewed the Python renderer, example schema, and response-model regression. The numeric regression is covered, but the new prefixItems handling is not semantically correct for unconstrained suffixes or schemas containing both prefixItems and items, and it loses named object models in tuple positions. Not LGTM.

💬 Posted 2 inline comments
  • 💡 MEDIUM lib/generators/python.ts:89 This treats prefixItems as the complete element domain, but JSON Schema allows additional unconstrained elements when items is absent; the added shape schema therefore permits [1, 2, "x"] while generating list[float]. When both keywords are present, this branch also drops the prefix types by selecting only items. Combine the prefix types with the items type, or include Any for an unconstrained suffix unless maxItems proves the tuple is bounded. turn0search3
  • 📝 LOW lib/generators/python.ts:522 For a valid tuple such as type: array, prefixItems: [{ "$ref": "#/components/schemas/Foo" }], this routes the item through pythonArrayItemType and then pythonType, which resolves object schemas to dict[str, Any]. Unlike the existing items path, it never calls modelType/addModel, so the generated SDK loses the referenced Foo TypedDict. Keep prefix-item model references on the model-aware path when generating response models.

Comment thread lib/generators/python.ts

function pythonArrayItemType(document: OpenApiDocument, schema: JsonSchema, collection = "list"): string {
const items = schema.items ? [schema.items] : (schema.prefixItems ?? []);
return [...new Set(items.map((item) => pythonType(document, item, collection)))].join(" | ") || "Any";

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

💡 MEDIUM: This treats prefixItems as the complete element domain, but JSON Schema allows additional unconstrained elements when items is absent; the added shape schema therefore permits [1, 2, "x"] while generating list[float]. When both keywords are present, this branch also drops the prefix types by selecting only items. Combine the prefix types with the items type, or include Any for an unconstrained suffix unless maxItems proves the tuple is bounded. turn0search3

Comment thread lib/generators/python.ts
const type = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
if (type === "array") return result(`list[${modelType(schema.items, `${name}Item`)}]`);
if (type === "array") {
const itemType = schema.items ? modelType(schema.items, `${name}Item`) : pythonArrayItemType(document, schema);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

📝 LOW: For a valid tuple such as type: array, prefixItems: [{ "$ref": "#/components/schemas/Foo" }], this routes the item through pythonArrayItemType and then pythonType, which resolves object schemas to dict[str, Any]. Unlike the existing items path, it never calls modelType/addModel, so the generated SDK loses the referenced Foo TypedDict. Keep prefix-item model references on the model-aware path when generating response models.

@glenn-jocher

Copy link
Copy Markdown
Member Author

Closing in favor of the smaller contract-owner fix. The review correctly identified that generic prefixItems handling must account for unconstrained suffixes, combined items, and model-aware tuple members. Portal's affected shapes are homogeneous fixed-length arrays and will be represented precisely there instead.

@glenn-jocher
glenn-jocher deleted the codex/python-prefix-items branch August 19, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fixed Bug has been resolved

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants