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.
Instead of maintaining separate CreateUser, ReadUser, UpdateUser, request
and response models by hand (or equivalent command models for RPC-style APIs),
you describe the fields each operation needs and Projector builds the output
models at runtime.
domain models -> typed field projection -> generated operation models
Projector is useful when your API, command, or service boundary needs many model variants that mostly differ by selected fields, requiredness, or output target.
Installation
pip install model-projector
The PyPI distribution is named model-projector. The Python import package is
named projector:
from projector import project, renderer, views_for
Projector requires Python 3.12 or newer.
Quickstart
from dataclasses import dataclass
from projector import project, optional, renderer, required, views_for
@dataclass(kw_only=True)
class Address:
city: str
zip: str
@dataclass(kw_only=True)
class User:
name: str
email: str
address: Address
views = views_for(User)
UserModels = project(
User,
renderer=renderer.Pydantic,
Create=(
required(views.name + views.email)
+ optional(views.address.city + views.address.zip)
),
Read=views.name + views.email + views.address.city,
Update=views.name,
)
UserCreate = UserModels.CreateModel
UserRead = UserModels.ReadModel
created = UserModels.Create(
name="Sam",
email="sam@example.com",
address={"city": "Paris", "zip": "75001"},
)
read = UserModels.Read(name="Sam", email="sam@example.com", address={"city": "Paris"})
The output namespace contains generated model classes and matching factory functions:
UserModels.CreateModel
UserModels.ReadModel
UserModels.UpdateModel
UserModels.Create(...)
UserModels.Read(...)
UserModels.Update(...)
The names Create, Read, and Update are conventions only. You can use
operation names that match your application:
UserCommands = project(
User,
renderer=renderer.Pydantic,
RegisterUser=views.name + views.email,
RenameUser=views.name,
)
Snake-case names are supported too:
UserModels = project(User, renderer=renderer.Pydantic, create_user=views.name)
UserModels.create_user(...)
UserModels.create_user_model
At a Glance
- Generate operation-specific models from existing domain models.
- Keep runtime model derivation in Python; no generated source files are needed to run your application.
- Generate optional
.pyifiles so type checkers and LSPs like ty and pyrefly and understand dynamic field selectors. - Projector can ingest models written using dataclasses, Pydantic models, attrs
classes,
TypedDictclasses, and plain annotated classes. - Projector can output: Pydantic, dataclass, attrs, or
TypedDictmodels.
Core Concepts
views_for(Model) returns a typed selector tree for a source model. Selectors
can be composed with + to describe the fields included in each output model.
project(Model, renderer=..., **views) compiles the selected fields and returns
a namespace containing generated model classes and factory functions.
required(...) and optional(...) override nullability for a selected subtree.
This lets a nullable source field be required in one operation and optional in
another.
Update is currently special-cased as a partial update model. Other output
names are treated as full models.
build_entity(Model) is the lower-level schema IR helper. Most users should
call project(...) with the source model class directly.
Supported Models
Projector can read these source model shapes:
- dataclasses
- Pydantic models
- attrs classes
TypedDictclasses- plain annotated classes
Projector can generate these output model shapes:
- Pydantic models
- dataclass models
- attrs classes
TypedDictclasses
Choose the output target with a renderer:
from projector import renderer
renderer.Pydantic
renderer.Dataclass
renderer.Attrs
renderer.TypedDict
Renderer classes are also exported directly:
from projector import PydanticRenderer, DataclassRenderer
Public API
Most users only need:
from projector import project, renderer, views_for
Additional helpers are exported for requiredness, direct renderer classes, stub generation, and lower-level schema inspection:
from projector import (
AttrsRenderer,
DataclassRenderer,
PydanticRenderer,
TypedDictRenderer,
UNSET,
build_entity,
generate_views_pyi,
optional,
required,
)
Type Checking
Projector derives models dynamically at runtime. Runtime use does not require generated files.
Static type checkers and LSPs need one extra step for dynamic views_for(...)
attributes. Generate sibling .pyi files with:
projector type-stubs path/to/models.py path/to/other_models.py
Example:
app/models.py -> app/models.pyi
Run this command after changing source model definitions. The generated stubs are for type checkers and language servers; they are not imported at runtime.
Examples
The repository includes two isolated examples:
examples/demo_example/ Small end-to-end demo
examples/fast_api_example/ FastAPI CRUD and command-style demo
From a checkout of this repository:
just demo-example
just fast-api-example
Development
Useful common commands:
just test
just check
just stubs
just demo-example
just fast-api-example
just check runs Ruff, ty, and pyrefly.
Contributing
Issues and pull requests are welcome. Before opening a change, run:
just test
just check
If you change example model definitions, regenerate their type stubs:
just stubs
Releasing
Releases are published to PyPI as model-projector by GitHub Actions using PyPI
Trusted Publishing. No PyPI API token is stored in the repository.
For maintainers, create a patch, minor, or major release with:
just bump patch
just bump minor
just bump major
The bump recipe updates the version with uv version, runs tests and checks,
pushes main, creates a matching vX.Y.Z tag, and pushes the tag. The tag
triggers the PyPI publishing workflow.
Manual workflow runs publish to TestPyPI only.
License
Projector is distributed under the MIT License. See LICENSE.
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.1.tar.gz.
File metadata
- Download URL: model_projector-0.1.1.tar.gz
- Upload date:
- Size: 19.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a538763333b639e50338f05cd947c63517137cfa7d0b64b5e3715a3ec16e673
|
|
| MD5 |
54294969d0a78b3c3afbdcfa3b8486e5
|
|
| BLAKE2b-256 |
1832a23aa1272155e0ad330b6c60229d01185256e4482d1bf93ff5cfc9492e98
|
Provenance
The following attestation bundles were made for model_projector-0.1.1.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.1.tar.gz -
Subject digest:
7a538763333b639e50338f05cd947c63517137cfa7d0b64b5e3715a3ec16e673 - Sigstore transparency entry: 2146542300
- Sigstore integration time:
-
Permalink:
sbernheim4/projector@35ca560d7b573a3dc55e90070c4d2db44e5876a9 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/sbernheim4
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@35ca560d7b573a3dc55e90070c4d2db44e5876a9 -
Trigger Event:
push
-
Statement type:
File details
Details for the file model_projector-0.1.1-py3-none-any.whl.
File metadata
- Download URL: model_projector-0.1.1-py3-none-any.whl
- Upload date:
- Size: 19.0 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 |
cb65a236bd307842c2ef30e1edbf8b3ee95a973081dfa4f0f52e6f3d9ecda06f
|
|
| MD5 |
11a7a1f8049b510bb0bde152ce2c4fa0
|
|
| BLAKE2b-256 |
c5a3a7ab23aafa34016bcf8d5e6d60740bd971269e108728da7414802c85d764
|
Provenance
The following attestation bundles were made for model_projector-0.1.1-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.1-py3-none-any.whl -
Subject digest:
cb65a236bd307842c2ef30e1edbf8b3ee95a973081dfa4f0f52e6f3d9ecda06f - Sigstore transparency entry: 2146542376
- Sigstore integration time:
-
Permalink:
sbernheim4/projector@35ca560d7b573a3dc55e90070c4d2db44e5876a9 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/sbernheim4
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@35ca560d7b573a3dc55e90070c4d2db44e5876a9 -
Trigger Event:
push
-
Statement type: