Skip to content

Comments

feat: static OpenAPI schema generation with cog-schema-gen#2774

Draft
tempusfrangit wants to merge 8 commits intomainfrom
feat/build-time-schema
Draft

feat: static OpenAPI schema generation with cog-schema-gen#2774
tempusfrangit wants to merge 8 commits intomainfrom
feat/build-time-schema

Conversation

@tempusfrangit
Copy link
Member

Summary

Replace runtime Python-based OpenAPI schema generation with a fast, static Rust-based generator (cog-schema-gen) that runs before the Docker build instead of after.

  • New Rust crate (crates/schema-gen/): tree-sitter-based Python parser that statically extracts predictor signatures and produces OpenAPI 3.0.2 JSON schemas — no Python runtime needed, runs in <1s
  • Build flow change: schema generation moves before Docker build (fail fast on schema errors); coglet reads .cog/openapi_schema.json from disk at runtime instead of calling Python
  • Removed runtime code: deleted openapi_schema.go (Docker-based generation), _schemas.py, command/openapi_schema.py, and Handler::schema() trait methods from coglet
  • Distribution: cog-schema-gen binary embedded in cog via go:embed with fallback resolution (COG_SCHEMA_GEN_BINARY env → embedded → dist/ → PATH); also shipped as standalone release artifact

What the parser handles

  • All predictor patterns: BasePredictor subclasses, non-BasePredictor classes, standalone functions, async methods
  • Type annotations: str, int, float, bool, Path, File, Secret, Any, Optional[X], X | None, list[X]/List[X], Iterator[X], ConcatenateIterator[X], AsyncIterator[X]
  • Input() kwargs: description, default, ge, le, min_length, max_length, regex, choices, deprecated
  • Shared Input definitions (cog-flux pattern): class attribute refs + static method calls with arg merging
  • BaseModel subclasses for structured output
  • Hard errors for default_factory and non-literal defaults

CI/Release integration

  • CI: build-schema-gen job builds once on ubuntu, artifact consumed by build-cog via goreleaser pre-hook
  • Release: 4-platform matrix (linux/darwin × amd64/arm64), embedded per-platform + standalone binaries attached to release
  • Binary size-optimized with release-small profile (~<900KB)

…ribution

Replace Docker container-based OpenAPI schema generation with a Rust binary
(cog-schema-gen) that uses tree-sitter to parse Python source files statically.
Schema generation now happens locally before the Docker build, not after.

Rust crate (crates/schema-gen/):
- Tree-sitter Python parser handles all predictor patterns: BasePredictor,
  non-BasePredictor classes, standalone functions, async methods
- Supports Input() kwargs, shared Input definitions (cog-flux pattern),
  Optional/union types, Iterator/ConcatenateIterator, BaseModel outputs
- Size-optimized release-small profile: LTO, opt-level=z, panic=abort,
  strip=symbols, no clap/anyhow — binary is ~879KB
- 19 unit tests, 22 integration test fixtures all passing

Go integration (pkg/schemagen/):
- Embed+exec pattern: binary embedded via go:embed, extracted to
  ~/.cache/cog/bin/cog-schema-gen-{version} on first use
- Resolution: COG_SCHEMA_GEN_BINARY env > embedded > dist/ > PATH
- pkg/image/build.go now calls schemagen.Generate() instead of booting
  a Docker container with python -m cog.command.openapi_schema

Build & CI:
- mise build:schema-gen uses --profile release-small
- mise build:cog depends on build:schema-gen, copies with platform suffix
- goreleaser per-build pre-hook embeds platform-matched binary
- CI: build-schema-gen job builds once, stashes artifact for build-cog
- Release: matrix build (4 platforms), standalone binaries attached to
  GitHub releases alongside cog CLI binaries
Schema generation now runs before the Docker build starts, failing fast
on schema errors before any container work begins.

Build side (pkg/image/build.go):
- Schema generation block moved above Docker image build
- .cog/openapi_schema.json is written before the build context is created
- No functional change to schema gen itself (still uses schemagen.Generate)

Coglet side (crates/coglet/src/worker.rs):
- Loads schema from .cog/openapi_schema.json instead of calling Python
  cog._schemas.to_json_schema() via PyO3 at runtime
- Missing schema file: clear warning, predictor accepts any input
- Corrupt schema file: clear warning, predictor accepts any input
- Images built with older cog versions (no schema file) continue to work
Now that coglet loads the OpenAPI schema from .cog/openapi_schema.json
(generated at build time by cog-schema-gen), the runtime Python schema
generation path is dead code.

Deleted:
- pkg/image/openapi_schema.go — Docker-based GenerateOpenAPISchema()
  that booted a container to run python -m cog.command.openapi_schema
- python/cog/_schemas.py — Python schema generation (to_json_schema)
- python/cog/command/openapi_schema.py — CLI entry point for above

Removed from Rust:
- Handler::schema() trait method from worker.rs (default None impl)
- WorkerBridge::schema() delegation in worker_bridge.rs
- PythonPredictor::schema() in predictor.rs that called cog._schemas
  via PyO3 at runtime

Still kept (used by coglet-python for input validation):
- python/cog/_inspector.py — check_input(), create_predictor()
- python/cog/_adt.py — PredictorInfo types, SDK detection
@tempusfrangit tempusfrangit requested a review from a team as a code owner February 24, 2026 22:38
@tempusfrangit tempusfrangit changed the title feat: static OpenAPI schema generation with cog-schema-ge feat: static OpenAPI schema generation with cog-schema-gen Feb 24, 2026
…lation

When SDK version is explicitly pinned below 0.17.0 (e.g. COG_SDK_WHEEL=pypi:0.16.12),
fall back to runtime schema generation and skip coglet installation:

- Add GenerateCombined() to merge predict+train schemas (fixes missing /trainings)
- Add canUseStaticSchemaGen() with binary availability check (graceful fallback)
- Add isLegacySDK() to skip coglet for SDK < 0.17.0
- Add DetectLocalSDKVersion() to resolve SDK version from dist/ wheels
- Restore GenerateOpenAPISchema() for legacy Docker-based schema generation
- Add legacy_sdk_schema integration test
Input validation is now handled at the HTTP edge using the OpenAPI schema
generated at build time. The worker no longer needs to introspect Python
types at runtime via _inspector/_adt.

Removed:
- python/cog/_adt.py (type ADT for runtime introspection)
- python/cog/_inspector.py (predictor introspection and input validation)
- python/tests/test_adt.py, python/tests/test_inspector.py

Simplified in coglet:
- input.rs: removed Runtime enum, InputProcessor trait, CogInputProcessor,
  detect_runtime(), try_cog_runtime(). Replaced with simple prepare_input()
  that only handles URLPath downloads.
- worker_bridge.rs: SDK detection uses cog.BasePredictor instead of cog._adt
- predictor.rs: removed runtime/input_processor fields from PythonPredictor
@tempusfrangit tempusfrangit marked this pull request as draft February 25, 2026 02:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant