Skip to main content

modmex

Lightweight Python models built on dataclasses with validation, serialization, and type-safe data mapping.

CI Coverage PyPI Python Versions License

modmex gives you a small but powerful toolkit for:

  • Typed models with minimal boilerplate.
  • Automatic coercion and validation at initialization time.
  • Recursive serialization to Python primitives and JSON.
  • Per-field and per-model validation hooks.
  • Type-based custom serializers to adapt output for different consumers.

Why modmex

If you want stricter models than plain dataclasses, but without the weight of a large framework, modmex is designed for that middle ground.

It focuses on:

  • Simplicity: small API surface.
  • Predictability: explicit model lifecycle.
  • Flexibility: configurable serialization without changing your model definitions.

Installation

With pip:

pip install modmex

With Poetry:

poetry add modmex

Quick Start

from decimal import Decimal

from modmex import BaseModel, Field


class User(BaseModel):
    id: int
    name: str
    balance: Decimal = Decimal("0")
    password: str = Field("", exclude=True)


user = User(id="1", name=123, balance="10.50")

# Type coercion happens during initialization.
assert user.id == 1
assert user.name == "123"
assert user.balance == Decimal("10.50")

# model_dump returns primitive/serializable values.
assert user.model_dump() == {
    "id": 1,
    "name": "123",
    "balance": 10.5,
}

# model_dump_json returns a JSON string.
assert user.model_dump_json() == '{"id":1,"name":"123","balance":10.5}'

Field Configuration

Use Field(...) to add serialization metadata to a model field.

Main options:

  • exclude=True
    • Always excludes this field from model_dump and model_dump_json.
  • exclude_from={"profile_name"}
    • Excludes this field only for selected serialization profiles.

Example:

from modmex import BaseModel, Field


class Session(BaseModel):
    id: str
    secret: str = Field("", exclude_from={"public"})


class User(BaseModel):
    id: int
    private_note: str = Field("x", exclude=True)
    sessions: list[Session] = Field(default_factory=list)


user = User(id=1, sessions=[Session(id="s1", secret="abc")])

assert user.model_dump(profile="public") == {
    "id": 1,
    "sessions": [{"id": "s1"}],
}

Tip:

  • Use exclude=True for values that should never leave the model.
  • Use exclude_from={...} when omission depends on the output profile.

Everyday Usage

1) Parse and normalize input data

from modmex import BaseModel


class Product(BaseModel):
    id: int
    name: str
    active: bool


product = Product(id="10", name=123, active="true")

assert product.id == 10
assert product.name == "123"
assert product.active is True

2) Work with nested models

from modmex import BaseModel


class Address(BaseModel):
    zipcode: int


class User(BaseModel):
    id: int
    address: Address


user = User(id="1", address={"zipcode": "90210"})
assert user.address.zipcode == 90210

3) Prepare different payloads from the same model

api_payload = account.model_dump(profile="public")
internal_payload = account.model_dump()

4) Build JSON directly

json_payload = account.model_dump_json(profile="public")

Dynamic Models

Use create_model when you need to define a model at runtime.

Field definitions can use one of these forms:

  • (annotation, default)
  • (annotation, Field(...))
  • typing.Annotated[annotation, Field(...)]
from typing import Annotated

from modmex import BaseModel, Field, create_model, field_validator, model_validator


def normalize_name(self, value: str) -> str:
    return value.strip().title()


DynamicUser = create_model(
    "DynamicUser",
    id=(int, ...),
    name=(str, Field(default="anonymous")),
    slug=Annotated[str, Field(default="user", exclude=True)],
    __validators__={"normalize_name": field_validator("name")(normalize_name)},
)


class StaticUser(BaseModel):
    id: int
    name: str = "anonymous"
    slug: str = Field(default="user", exclude=True)

    @field_validator("name")
    def normalize_name(self, value: str) -> str:
        return value.strip().title()


dynamic_user = DynamicUser(id="1", name="  john ")
static_user = StaticUser(id="1", name="  john ")

assert dynamic_user.model_dump() == static_user.model_dump()
assert dynamic_user.name == static_user.name == "Jhon"

Validators

Field validators

Use @field_validator("field_name") to transform or validate a single field.

from modmex import BaseModel, field_validator


class Product(BaseModel):
    name: str

    @field_validator("name")
    def normalize_name(self, value: str) -> str:
        return value.strip().title()

Model validators

Use @model_validator(mode="before" | "after") to work with full model state.

  • before: runs before type coercion.
  • after: runs after field-level validation.
from modmex import BaseModel, model_validator


class Product(BaseModel):
    name: str
    slug: str = ""

    @model_validator(mode="before")
    def build_slug(self, values: dict) -> dict:
        values["slug"] = values["name"].lower().replace(" ", "-")
        return values

Serialization

model_dump(...)

Use model_dump when you need a dictionary payload.

Most common options:

  • exclude={...} to omit fields for a specific call.
  • profile="..." to apply exclude_from rules.
  • include_excluded=True to force metadata-excluded fields into the payload.
  • type_serializers={...} to control how specific Python types are represented.

model_dump_json(...)

Use model_dump_json when you need a JSON string output.

It supports the same practical options as model_dump (exclude, profile, include_excluded, type_serializers).

Omitting Fields During Serialization

Use this feature when the same model must produce different payloads depending on where the data is going.

  • exclude_from defines where a field should be omitted.
  • profile selects which omission rules to apply in a specific dump call.

What each option does

  • exclude_from={"public"}
    • Omit this field when serializing with profile="public".
  • profile="public"
    • Apply all field rules tagged for public during serialization.

Common pattern

You may want one shape for API responses and another for internal flows (logs, queues, exports, persistence payloads, etc.).

  • API payload (profile="public"): hide internal fields.
  • Internal payload (no profile, or another profile): keep those fields.

Example

from modmex import BaseModel, Field


class Account(BaseModel):
    id: int
    email: str = Field("", exclude_from={"public"})
    internal_note: str = Field("", exclude=True)


account = Account(id=1, email="a@x.com", internal_note="secret")

# No profile: only always-excluded fields are removed.
assert account.model_dump() == {
    "id": 1,
    "email": "a@x.com",
}

# public profile: profile-based exclusions are applied.
assert account.model_dump(profile="public") == {
    "id": 1,
}

# include_excluded=True: ignore Field exclusion metadata.
assert account.model_dump(profile="public", include_excluded=True) == {
    "id": 1,
    "email": "a@x.com",
    "internal_note": "secret",
}

# Dynamic omission for one call (without metadata changes).
assert account.model_dump(exclude={"email"}) == {
    "id": 1,
}

Type-Based Custom Serializers

You can override serialization behavior by type with type_serializers.

Shape:

type_serializers = {
    SomeType: serializer_function,
}

Keep Decimal values as Decimal in model_dump

from decimal import Decimal

dumped = model.model_dump(
    type_serializers={
        Decimal: lambda value: value,
    }
)

Convert float to Decimal for a specific output contract

from decimal import Decimal

from modmex import BaseModel


class Price(BaseModel):
    amount: float


p = Price(amount=10.25)
dumped = p.model_dump(
    type_serializers={
        float: lambda value: Decimal(str(value)),
    }
)

assert dumped["amount"] == Decimal("10.25")

Emit Decimal as string in JSON

from decimal import Decimal

dumped_json = model.model_dump_json(
    type_serializers={
        Decimal: lambda value: str(value),
    }
)

Note: some client libraries expect Decimal instead of float values (for example, common boto3 workflows). Type serializers let you adapt output contracts cleanly, without hard-coding backend-specific behavior into your models.

Error Handling

Validation issues raise ValidationError.

Each error includes:

  • loc: location path (supports nested structures).
  • msg: human-readable message.
  • type: error category.

Example locations:

  • ["address", "zipcode"]
  • ["tags", 1]

Practical Usage Pattern

Use this rule of thumb:

  • Keep rich Python types in the in-memory model instance.
  • Use model_dump / model_dump_json to produce transport-friendly payloads.
  • Use type_serializers when a specific consumer requires a different type format.

Compatibility

  • Python 3.10+

Download files

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

Source Distribution

modmex-1.2.2.tar.gz (32.0 kB view details)

Uploaded Source

Built Distributions

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

modmex-1.2.2-cp310-abi3-win_amd64.whl (181.1 kB view details)

Uploaded CPython 3.10+Windows x86-64

modmex-1.2.2-cp310-abi3-manylinux_2_34_x86_64.whl (274.0 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.34+ x86-64

modmex-1.2.2-cp310-abi3-macosx_11_0_arm64.whl (250.4 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

File details

Details for the file modmex-1.2.2.tar.gz.

File metadata

  • Download URL: modmex-1.2.2.tar.gz
  • Upload date:
  • Size: 32.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for modmex-1.2.2.tar.gz
Algorithm Hash digest
SHA256 ed64afade2a96afe5d3b3564618d1244f54f56654752eae8986c2c0b4325d8f6
MD5 a61553b51f547ae6ad321233d772750a
BLAKE2b-256 d6c7519e6342d40879028213f4728b06e0f468afe0865be2207a7c0eb3a2fc0c

See more details on using hashes here.

Provenance

The following attestation bundles were made for modmex-1.2.2.tar.gz:

Publisher: release.yml on modmex/modmex

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

File details

Details for the file modmex-1.2.2-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: modmex-1.2.2-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 181.1 kB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for modmex-1.2.2-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 6314932ac28c1ec7168a0bf211b70812cfbb619390db09a110ad31735cca102b
MD5 2d5e788c566ceb85222691cebfe14533
BLAKE2b-256 a7f1161057e83452ecb896f9e03574f2b76e06cbe22405bf60eeb8b756370be8

See more details on using hashes here.

Provenance

The following attestation bundles were made for modmex-1.2.2-cp310-abi3-win_amd64.whl:

Publisher: release.yml on modmex/modmex

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

File details

Details for the file modmex-1.2.2-cp310-abi3-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for modmex-1.2.2-cp310-abi3-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 593a786ccb9f295fd00324129e93d919fdb9c5a679ed65cf68e990a4cc7fa966
MD5 747885700d92c41ee8e0a5ca3305575b
BLAKE2b-256 25a9b7aa94e495ef1cd7ec9d36b6b47c669ff6c8ba2462a2307b186375f51d65

See more details on using hashes here.

Provenance

The following attestation bundles were made for modmex-1.2.2-cp310-abi3-manylinux_2_34_x86_64.whl:

Publisher: release.yml on modmex/modmex

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

File details

Details for the file modmex-1.2.2-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for modmex-1.2.2-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e5a4be90cb8639f05fa0e9864d67dc956a54e4d02f078725a7277ec03dc64548
MD5 993942c28b29d0009998e9dd8198aaa4
BLAKE2b-256 dcfadd016feb85ff2197e05d390e0ed979a5245f80b26a11841bcc35224fa79f

See more details on using hashes here.

Provenance

The following attestation bundles were made for modmex-1.2.2-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release.yml on modmex/modmex

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

Release history Release notifications | RSS feed

This release

1.2.2 This release

4 files

1.2.1

4 files

1.2.0

4 files

1.1.15

4 files

1.1.14

4 files

1.1.13

4 files

1.1.12

4 files

1.1.11

4 files

1.1.10

4 files

1.1.9

4 files

1.1.8

4 files

1.1.7

4 files

1.1.6

4 files

1.1.5

4 files

1.1.4

4 files

1.1.3

4 files

1.1.2

3 files

1.0.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page