Skip to main content

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 .pyi files 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, TypedDict classes, and plain annotated classes.
  • Projector can output: Pydantic, dataclass, attrs, or TypedDict models.

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
  • TypedDict classes
  • plain annotated classes

Projector can generate these output model shapes:

  • Pydantic models
  • dataclass models
  • attrs classes
  • TypedDict classes

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

model_projector-0.1.1.tar.gz (19.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

model_projector-0.1.1-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

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

Hashes for model_projector-0.1.1.tar.gz
Algorithm Hash digest
SHA256 7a538763333b639e50338f05cd947c63517137cfa7d0b64b5e3715a3ec16e673
MD5 54294969d0a78b3c3afbdcfa3b8486e5
BLAKE2b-256 1832a23aa1272155e0ad330b6c60229d01185256e4482d1bf93ff5cfc9492e98

See more details on using hashes here.

Provenance

The following attestation bundles were made for model_projector-0.1.1.tar.gz:

Publisher: publish.yml on sbernheim4/projector

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for model_projector-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 cb65a236bd307842c2ef30e1edbf8b3ee95a973081dfa4f0f52e6f3d9ecda06f
MD5 11a7a1f8049b510bb0bde152ce2c4fa0
BLAKE2b-256 c5a3a7ab23aafa34016bcf8d5e6d60740bd971269e108728da7414802c85d764

See more details on using hashes here.

Provenance

The following attestation bundles were made for model_projector-0.1.1-py3-none-any.whl:

Publisher: publish.yml on sbernheim4/projector

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page