Skip to main content

Lightweight mixin for generic introspection and Annotated field lookup

Project description

Pydantic Super Model

Coverage

A lightweight mixin for generic type introspection and Annotated field lookup. Works with any Python class, with optional Pydantic integration.

Two classes:

Class Base Key extras
SuperModelMixin Any class Framework-agnostic annotation introspection
SuperModelPydanticMixin Pydantic BaseModel Auto FieldNotImplemented validation, omits unset default None values
from pydantic_super_model import AnnotatedFieldInfo, FieldNotImplemented, SuperModelMixin, SuperModelPydanticMixin

Installation

pip install pydantic-super-model

Quick Start

With any Python class

from typing import Annotated

from pydantic_super_model import SuperModelMixin


class PrimaryKeyAnnotation:
    pass


PrimaryKey = Annotated[int, PrimaryKeyAnnotation]


class User(SuperModelMixin):
    id: PrimaryKey
    name: str

    def __init__(self, id: PrimaryKey, name: str) -> None:
        self.id = id
        self.name = name


user = User(id=1, name="John Doe")
field_info = user.get_annotated_fields(PrimaryKey)["id"]

assert field_info.value == 1
assert field_info.annotation == PrimaryKey
assert field_info.metadata == (PrimaryKeyAnnotation,)

With Pydantic

from typing import Annotated

from pydantic_super_model import SuperModelPydanticMixin


class PrimaryKeyAnnotation:
    pass


PrimaryKey = Annotated[int, PrimaryKeyAnnotation]


class User(SuperModelPydanticMixin):
    id: PrimaryKey
    name: str


user = User(id=1, name="John Doe")
field_info = user.get_annotated_fields(PrimaryKey)["id"]

assert field_info.value == 1
assert field_info.annotation == PrimaryKey
assert field_info.metadata == (PrimaryKeyAnnotation,)

API

get_annotated_fields(*annotations)

Return a dictionary of field names to AnnotatedFieldInfo for fields whose type hints carry any requested annotation.

  • Match by the full Annotated[...] alias or by metadata type
  • Include falsy values such as 0

SuperModelPydanticMixin only: unset default None values are omitted. Explicitly provided None is included.

class UserOptional(SuperModelPydanticMixin):
    id: PrimaryKey | None = None
    name: str


# Unset default None is omitted
assert not UserOptional(name="A").get_annotated_fields(PrimaryKey)

# Explicitly provided None is included
field_info = UserOptional(id=None, name="B").get_annotated_fields(PrimaryKey)["id"]
assert field_info.value is None

SuperModelMixin: all None values are included regardless of whether they were explicitly set.

get_annotated_field_value(annotation, allow_none=False, allow_undefined=False)

Return the first matching AnnotatedFieldInfo.

  • Raises ValueError if no matching field exists (unless allow_undefined=True)
  • Raises ValueError if the matched value is None (unless allow_none=True)
field_info = user.get_annotated_field_value(PrimaryKey)

assert field_info.value == 1

get_type()

Return the concrete generic type parameter supplied to the instance, or None.

from typing import Generic, TypeVar

from pydantic_super_model import SuperModelMixin

GenericType = TypeVar("GenericType")


class UserWithType(SuperModelMixin, Generic[GenericType]):
    id: GenericType
    name: str

    def __init__(self, id: GenericType, name: str) -> None:
        self.id = id
        self.name = name


assert UserWithType[int](id=1, name="Charlie").get_type() is int

validate_not_implemented_fields()

Reject fields annotated with FieldNotImplemented. Raises NotImplementedError if any such fields have values.

SuperModelPydanticMixin: called automatically on construction.

from typing import Annotated

from pydantic_super_model import FieldNotImplemented, SuperModelPydanticMixin


class Experimental(SuperModelPydanticMixin):
    test_field: Annotated[int, FieldNotImplemented]
    name: str


Experimental(test_field=1, name="x")  # raises NotImplementedError

SuperModelMixin: call manually in __init__ or __post_init__.

from typing import Annotated

from pydantic_super_model import FieldNotImplemented, SuperModelMixin


class Experimental(SuperModelMixin):
    test_field: Annotated[int, FieldNotImplemented]
    name: str

    def __init__(self, test_field: int, name: str) -> None:
        self.test_field = test_field
        self.name = name
        self.validate_not_implemented_fields()


Experimental(test_field=1, name="x")  # raises NotImplementedError

AnnotatedFieldInfo

A NamedTuple returned by get_annotated_fields and get_annotated_field_value:

Field Type Description
value Any The field's current value
annotation object The full type annotation
metadata tuple[object, ...] All metadata from Annotated
matched_metadata tuple[object, ...] Only the metadata that matched the query

Metadata Instance Matching

When you pass a class (not an instance) to get_annotated_fields, it matches metadata by isinstance:

from typing import Annotated

from pydantic_super_model import SuperModelPydanticMixin


class ThemeColorOptions:
    def __init__(self, *, palette: str, allow_gradients: bool) -> None:
        self.palette = palette
        self.allow_gradients = allow_gradients


ThemeColorField = Annotated[
    str,
    "theme_color",
    ThemeColorOptions(palette="northern-lights", allow_gradients=True),
]


class ThemeConfig(SuperModelPydanticMixin):
    accent_color: ThemeColorField


theme = ThemeConfig(accent_color="#7dd3fc")
field_info = theme.get_annotated_fields(ThemeColorOptions)["accent_color"]

assert isinstance(field_info.matched_metadata[0], ThemeColorOptions)
assert field_info.matched_metadata[0].palette == "northern-lights"
assert field_info.matched_metadata[0].allow_gradients is True

Development

Install dev dependencies:

pip install "pydantic-super-model[dev]"

Run the test suite:

pytest

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

pydantic_super_model-2.0.1.tar.gz (5.5 kB view details)

Uploaded Source

Built Distribution

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

pydantic_super_model-2.0.1-py3-none-any.whl (7.7 kB view details)

Uploaded Python 3

File details

Details for the file pydantic_super_model-2.0.1.tar.gz.

File metadata

  • Download URL: pydantic_super_model-2.0.1.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pydantic_super_model-2.0.1.tar.gz
Algorithm Hash digest
SHA256 aaedb496d2f2f21fa59ec7eefbf601fc6bcd869a8147b0851a6318b68d1b5345
MD5 58d4cca655c5ea628f56298eb1225553
BLAKE2b-256 57e7c9d98b8f4cd180fe8996a4a50f8770ae64b57da30cc28a2a674691424e09

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_super_model-2.0.1.tar.gz:

Publisher: publish-to-pypi.yml on julien777z/pydantic-super-model

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

File details

Details for the file pydantic_super_model-2.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for pydantic_super_model-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 027594314cbace3ebb1944be08c6008faf4a7fa39a4c2194a0aafb59e1e5795c
MD5 5250aa639c080e775816a06e23e8245c
BLAKE2b-256 89ef9841be13b97f049ee2f12bd14c1d7d09fba97f81d575b2621b752d79cd85

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydantic_super_model-2.0.1-py3-none-any.whl:

Publisher: publish-to-pypi.yml on julien777z/pydantic-super-model

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