Skip to content

Include custom Python CLIs in generated SDKs - #50

Open
JaviChulvi wants to merge 5 commits into
ultralytics:mainfrom
JaviChulvi:feat/python-cli-introspection
Open

Include custom Python CLIs in generated SDKs#50
JaviChulvi wants to merge 5 commits into
ultralytics:mainfrom
JaviChulvi:feat/python-cli-introspection

Conversation

@JaviChulvi

@JaviChulvi JaviChulvi commented Sep 7, 2026

Copy link
Copy Markdown

Why

SDK repositories need to ship their maintained CLI through normal package regeneration. Optional python.cli configuration lets the SDK install a command such as ul while keeping product-specific behavior in the SDK repository.

Design

The generator copies the CLI source unchanged, registers its console command, and writes a small module identifying multipart file fields that SDK annotations cannot describe. The consumer owns main() and its module launcher; SDK signatures and docstrings provide the remaining argument information.

Keeping metadata in a separate module lets ordinary imports handle initialization and preserves the consumer's docstrings and future imports. Existing SDK generation stays unchanged when CLI support is not configured.

Validation: 20 generator tests, typecheck, lint, and unused-code checks pass. Package tests run both the console command and module launcher, and verify cleanup when CLI support is disabled.

Merge before sdk#59, whose CI regenerates from generator main.

@UltralyticsAssistant UltralyticsAssistant added documentation Improvements or additions to documentation enhancement New feature or request labels Sep 7, 2026
@UltralyticsAssistant

Copy link
Copy Markdown
Member

👋 Hello @JaviChulvi, 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! 🚀

@JaviChulvi
JaviChulvi marked this pull request as draft September 7, 2026 22:51

@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 configuration validation, Python generation, multipart metadata assembly, package-script registration, cleanup behavior, and focused test. The opt-in path is otherwise scoped and preserves default generation, but source-side __main__ launchers can cause failure or duplicate command execution when the generated module is run directly.

💬 Posted 1 inline comment
  • 💡 MEDIUM lib/generators/python.ts:885 Appending the generated launcher after the consumer source makes a normal executable cli.py with its own if __name__ == "__main__": main() block unsafe: direct execution runs main() before MULTIPART_FILES is assigned, which can raise NameError, or runs the command twice. The documented contract only requires a zero-argument main() and does not forbid a source-side launcher. Reject or strip an existing launcher, or explicitly enforce a module-only source contract before generating th…

@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 2

Made with ❤️ by Ultralytics Actions

Since review 1, the source-side launcher issue is addressed by rejecting consumer modules that reference __main__, with focused coverage for both successful generation and rejection. I also reviewed configuration validation, multipart metadata, package-script registration, cleanup behavior, and header insertion. No remaining behavioral defects found. LGTM.

@JaviChulvi
JaviChulvi marked this pull request as ready for review September 7, 2026 23:00
@JaviChulvi
JaviChulvi marked this pull request as draft September 7, 2026 23:01
@UltralyticsAssistant
UltralyticsAssistant dismissed their stale review September 7, 2026 23:49

Superseded by new review

@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 3

Made with ❤️ by Ultralytics Actions

Since review 2, the prior premature-execution issue is addressed by keeping the consumer launcher unchanged and moving multipart metadata to a separate module. Two risks remain: the focused test only validates direct-file execution with an import that fails for packaged console/module entrypoints, and the README still describes the removed generator-owned launcher contract.

💬 Posted 2 inline comments
  • 💡 MEDIUM lib/generators/python.test.ts:106 This fixture imports _cli_metadata as a top-level module and is only executed by filename on line 117, where the package directory happens to be on sys.path. The registered console script and python -m <package>.cli import <package>.cli; _cli_metadata.py is a sibling package module, so this source raises ModuleNotFoundError there. Use a package-relative import and exercise the registered/module entrypoints so the core CLI path is actually validated.
  • 📝 LOW README.md:59 This documents the behavior removed by the current implementation: the generator no longer appends MULTIPART_FILES or a __main__ block, and it no longer rejects __main__; the consumer source now owns its launcher and must import the generated metadata module. As written, consumers following this new configuration will receive an obsolete contract and can generate a non-working CLI. Update this paragraph before release.

@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 4

Made with ❤️ by Ultralytics Actions

Since review 3, both reported issues are addressed: the fixture now uses a package-relative metadata import and exercises both module and registered console entrypoints, while the README documents the consumer-owned launcher and generated metadata contract. No remaining or newly introduced behavioral defects found. LGTM.

@JaviChulvi JaviChulvi changed the title Support consumer-owned Python CLI modules Include custom Python CLIs in generated SDKs Sep 8, 2026
@JaviChulvi
JaviChulvi marked this pull request as ready for review September 8, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants