Derive operation-specific API models from existing Python domain models.
Project description
Projector
Eliminate model explosion by deriving fully typed, operation-specific Python models from existing domain models by declaratively specifying the properties you want.
Your application domain models (the input to projector) can be written using: dataclasses, Pydantic models, TypedDict classes, attrs classes, and plain annotated classes.
Projector can output the derived models in: Pydantic, dataclass, attrs, or TypedDict.
Installation
pip install model-projector
The distribution name is model-projector, and the import package is
projector:
from projector import project, renderer, views_for
Example
import sqlite3
from dataclasses import dataclass
from projector import project, optional, renderer, required, views_for
# Application specific domain models:
@dataclass(kw_only=True)
class Address:
city: str
zip: str
@dataclass(kw_only=True)
class User:
name: str
address: Address
# Derive operation-specific models.
views = views_for(User)
UserModels = project(
User,
renderer=renderer.Pydantic,
Create=required(views.name) + optional(views.address.city + views.address.zip),
Read=optional(views.name) + optional(views.address.city),
Update=views.name,
)
UserCreate = UserModels.CreateModel
# Use the derived user models.
conn = sqlite3.connect(":memory:")
conn.execute("create table users (name text, city text, zip text)")
def add_user_to_db(user: UserCreate) -> None:
conn.execute(
"insert into users (name, city, zip) values (?, ?, ?)",
(user.name, user.address.city, user.address.zip),
)
conn.commit()
# Create an instance with the exact keyword argument provided
new_user = UserModels.Create(name="John", address={"city": "Paris", "zip": "75001"})
add_user_to_db(new_user)
More Info
Create, Read, and Update above are arbitrary. Use whatever names fit your
application.
For example:
UserModels = project(
User,
renderer=renderer.Pydantic,
CreateUserCommand=views.name + views.address.city + views.address.zip,
Read=views.name + views.address.city,
UpdateNameCommand=views.name,
)
That gives you:
UserModels.CreateUserCommandModelUserModels.ReadModelUserModels.UpdateNameCommandModel
And functions to instantiate instances:
UserModels.CreateUserCommand(...)UserModels.Read(...)UserModels.UpdateNameCommand(...)
If a source model field can be None, you can still decide whether a specific
output must require it or keep it optional:
UserModels = project(
User,
renderer=renderer.Pydantic,
Create=required(views.address.city),
Read=optional(views.address.city),
)
required(...) applies to the whole selected subtree. optional(...) keeps
that subtree nullable in the generated output.
snake_case output names are supported too. If you use create or
create_user_command, the generated accessors stay snake_case:
UserModels.createUserModels.create_modelUserModels.create_user_commandUserModels.create_user_command_model
Supported input and output types
Input:
- dataclasses
- Pydantic models
- plain annotated classes
- attrs classes
TypedDictclasses
Output:
- Pydantic models
- dataclass models
- attrs classes
TypedDictclasses
Core Concepts
project(...) is the main runtime entry point. It takes a source model class,
selected fields, and a renderer, then returns a namespace containing generated
model classes and factory functions.
views_for(...) creates typed selectors for a source model. Use these selectors
to describe which fields belong in each generated output model.
projector type-stubs is the type-checker build step. It writes sibling .pyi
files so LSPs and type checkers can understand dynamic views_for(...)
attributes. These files are not executed at runtime.
build_entity(...) is the lower-level IR helper. Normal users should not need
it for project(...), but it remains public for debugging, tests, and tooling
that wants to inspect the derived schema.
Type Checking Build Step
Projector currently derives models dynamically at runtime. Runtime use does not
require generated files, but static type checkers like ty and pyrefly need
.pyi files to understand the dynamic view objects returned by
views_for(...).
Generate those type checker stubs with the CLI:
projector type-stubs path/to/models.py path/to/other_models.py
The command accepts one or more Python file paths and writes sibling .pyi
files:
examples/demo_example/models.py -> examples/demo_example/models.pyi
Example Layout
The examples/ directory contains fully isolated consumer examples.
examples/demo_example/ Demo domain models and runnable example
examples/fast_api_example/ An example with a FastAPI HTTP server with CRUD and command style generated models
Run the demo example:
just demo-example
# or
PYTHONPATH=src uv run python -m examples.demo_example.main
Run the FastAPI example:
just fast-api-example
# or
PYTHONPATH=src .venv/bin/python -m examples.fast_api_example.http.main
Helpful just commands
just test
just check
just stubs
just demo-example
just fast-api-example
Publishing
Projector is configured as a Python package in pyproject.toml. The package
uses the model-projector distribution name because projector is already
taken on PyPI.
Build the source distribution and wheel:
uv build
# or
python -m build
Validate the built package metadata:
python -m twine check dist/*
Upload to TestPyPI first:
python -m twine upload --repository testpypi dist/*
Install-test from TestPyPI:
python -m pip install \
--index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ \
model-projector
Upload the same distributions to PyPI after the TestPyPI package installs and imports correctly:
python -m twine upload dist/*
PyPI and TestPyPI require separate accounts and API tokens.
Library Structure
projector.ir
Builds the schema IR from user models.
Entityis a structured schema nodeFieldis a primitive leaf nodebuild_entity()introspects supported input models into the lower-level IR
projector.projection
Builds and compiles typed projections.
views_for()returns a tree of selectable fieldsLeafsupports+compositioncompile_projection()turns selections into nested specs
projector.renderers
Turns the IR/spec into concrete output classes.
PydanticRendererDataclassRendererAttrsRendererTypedDictRendererrenderer.Pydanticrenderer.Dataclassrenderer.Attrsrenderer.TypedDict
projector.encode
Public orchestration helpers.
project()takes a source model class and builds operation-specific factoriesbuild_model_and_factory()wires renderer output to factories
projector.stubgen
Stub generation helpers.
generate_views_pyi()writes a.pyifile next to a consumer model module
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file model_projector-0.1.0.tar.gz.
File metadata
- Download URL: model_projector-0.1.0.tar.gz
- Upload date:
- Size: 19.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3679da93a3de5855961da254263ecd1c582f47813bb5ebe6a38a1cd1b3c324e4
|
|
| MD5 |
b820ec2fca3fa1e2b4aa28e678e91031
|
|
| BLAKE2b-256 |
952187c199727b2d2af38d3278812130a9898c12e1a9509d3d71f6989123e171
|
Provenance
The following attestation bundles were made for model_projector-0.1.0.tar.gz:
Publisher:
publish.yml on sbernheim4/projector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
model_projector-0.1.0.tar.gz -
Subject digest:
3679da93a3de5855961da254263ecd1c582f47813bb5ebe6a38a1cd1b3c324e4 - Sigstore transparency entry: 2146470405
- Sigstore integration time:
-
Permalink:
sbernheim4/projector@c63ce808f7274958fa6a45298f7b2170aec3fa4a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sbernheim4
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c63ce808f7274958fa6a45298f7b2170aec3fa4a -
Trigger Event:
push
-
Statement type:
File details
Details for the file model_projector-0.1.0-py3-none-any.whl.
File metadata
- Download URL: model_projector-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
977fd6426b4c7e6daa5027f3791472ce4a6018b61a120cfb0d4782c59ece5673
|
|
| MD5 |
ef316c6362e8f7b0ab68134244196cca
|
|
| BLAKE2b-256 |
927a60a0ba63cad138a947ffd3b084e93198d845b397b254d3caddc255985255
|
Provenance
The following attestation bundles were made for model_projector-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on sbernheim4/projector
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
model_projector-0.1.0-py3-none-any.whl -
Subject digest:
977fd6426b4c7e6daa5027f3791472ce4a6018b61a120cfb0d4782c59ece5673 - Sigstore transparency entry: 2146470419
- Sigstore integration time:
-
Permalink:
sbernheim4/projector@c63ce808f7274958fa6a45298f7b2170aec3fa4a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/sbernheim4
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@c63ce808f7274958fa6a45298f7b2170aec3fa4a -
Trigger Event:
push
-
Statement type: