Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ validate-examples: ## validate examples in the specification markdown files
.PHONY: test
test:
go test ./...

.PHONY: generate-python-api
generate-python-api: ## generate Python API models from JSON schema
python3 tools/generate_python_models.py
44 changes: 44 additions & 0 deletions py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Python ModelPack Types

This directory provides auto-generated Python data structures for the ModelPack specification.

The models are generated from the canonical JSON Schema at `schema/config-schema.json` and are intended for downstream projects that need importable spec-aligned types.

## Requirements

- Python >= 3.10
- Pydantic >= 2

## Installation / Import setup

These models live under the `py/` directory.

To make `model_spec.v1` importable locally:

```bash
export PYTHONPATH="$(pwd)/py:${PYTHONPATH}"
```

## Usage

```python
from model_spec.v1 import Model

model = Model.model_validate_json(json_payload)
print(model.descriptor.docURL)
```

## Regenerate

Run:

```bash
pip install datamodel-code-generator
make generate-python-api
```

This executes `tools/generate_python_models.py`, which uses `datamodel-codegen` to regenerate `py/model_spec/v1/models.py`.

## Important

Do not edit generated models manually. Update the schema and regenerate instead.
19 changes: 19 additions & 0 deletions py/model_spec/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from .models import (
Model,
ModelCapabilities,
ModelConfig,
ModelDescriptor,
ModelFS,
Modality,
Language,
)

__all__ = [
"Model",
"ModelCapabilities",
"ModelConfig",
"ModelDescriptor",
"ModelFS",
"Modality",
"Language",
]
79 changes: 79 additions & 0 deletions py/model_spec/v1/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# generated by datamodel-codegen:
# filename: config-schema.json

from __future__ import annotations

from typing import Literal

from pydantic import AwareDatetime, BaseModel, ConfigDict, Field, RootModel


class ModelDescriptor(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
createdAt: AwareDatetime | None = None
authors: list[str] | None = None
family: str | None = None
name: str | None = Field(None, min_length=1)
docURL: str | None = None
sourceURL: str | None = None
datasetsURL: list[str] | None = None
version: str | None = None
revision: str | None = None
vendor: str | None = None
licenses: list[str] | None = None
title: str | None = None
description: str | None = None


class ModelFS(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
type: Literal['layers']
diffIds: list[str] = Field(..., min_length=1)


class Language(RootModel[str]):
root: str = Field(..., pattern='^[a-z]{2}$')


class Modality(
RootModel[Literal['text', 'image', 'audio', 'video', 'embedding', 'other']]
):
root: Literal['text', 'image', 'audio', 'video', 'embedding', 'other']


class ModelCapabilities(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
inputTypes: list[Modality] | None = None
outputTypes: list[Modality] | None = None
knowledgeCutoff: AwareDatetime | None = None
reasoning: bool | None = None
toolUsage: bool | None = None
reward: bool | None = None
languages: list[Language] | None = None


class ModelConfig(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
architecture: str | None = None
format: str | None = None
paramSize: str | None = None
precision: str | None = None
quantization: str | None = None
capabilities: ModelCapabilities | None = None


class Model(BaseModel):
model_config = ConfigDict(
extra='forbid',
)
descriptor: ModelDescriptor
modelfs: ModelFS
config: ModelConfig
71 changes: 71 additions & 0 deletions tools/generate_python_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Generate Python models from the canonical ModelPack JSON Schema."""

from __future__ import annotations

import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parent.parent
SCHEMA_PATH = ROOT / "schema" / "config-schema.json"
OUTPUT_PATH = ROOT / "py" / "model_spec" / "v1" / "models.py"


def main() -> int:
try:
import datamodel_code_generator # noqa: F401
except ModuleNotFoundError:
print(
"error: datamodel-code-generator is not installed for this Python interpreter. "
"Install it with: python -m pip install datamodel-code-generator",
file=sys.stderr,
)
return 1

if not SCHEMA_PATH.is_file():
print(
f"error: JSON Schema file not found or not a file at expected path: {SCHEMA_PATH}",
file=sys.stderr,
)
return 1

OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)

cmd = [
sys.executable,
"-m",
"datamodel_code_generator",
"--input",
str(SCHEMA_PATH),
"--output",
str(OUTPUT_PATH),
"--input-file-type",
"jsonschema",
"--output-model-type",
"pydantic_v2.BaseModel",
"--target-python-version",
"3.10",
"--enum-field-as-literal",
"all",
"--field-constraints",
"--disable-timestamp",
]

try:
subprocess.run(cmd, check=True)
except subprocess.CalledProcessError as exc:
cmd_str = " ".join(exc.cmd) if getattr(exc, "cmd", None) else " ".join(cmd)
print(
f"error: datamodel-code-generator failed with exit code {exc.returncode}.",
file=sys.stderr,
)
print(f"command: {cmd_str}", file=sys.stderr)
return exc.returncode or 1
else:
print(f"Generated: {OUTPUT_PATH}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading