Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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)
```
Comment thread
rishi-jat marked this conversation as resolved.

## Regenerate

Run:

```bash
pip install datamodel-code-generator
make generate-python-api
```
Comment thread
rishi-jat marked this conversation as resolved.

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,
)
Comment thread
rishi-jat marked this conversation as resolved.

__all__ = [
"Model",
"ModelCapabilities",
"ModelConfig",
"ModelDescriptor",
"ModelFS",
"Modality",
"Language",
]
Comment thread
rishi-jat marked this conversation as resolved.
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"
Comment thread
rishi-jat marked this conversation as resolved.


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",
Comment thread
rishi-jat marked this conversation as resolved.
]

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