Skip to main content

📦 Value Object Pattern

CI Pipeline Coverage Pipeline Package Version Supported Python Versions Package Downloads

The Value Object Pattern is a Python 🐍 package for building immutable, self-validating value objects 📦. It helps move validation, normalization, primitive conversion, and domain-specific constraints out of scattered application code and into small typed objects that can be reused across models, services, APIs, and tests.

Table of Contents

🔼 Back to top



📥 Installation

You can install Value Object Pattern using pip:

pip install value-object-pattern

You can install the companion AI-agent skill from skills.sh with Vercel's skills CLI:

npx skills add adriamontoto/value-object-pattern

Review the skill source in skills/value-object-pattern before installing it in sensitive environments.

🔼 Back to top



📚 Documentation

The root README is the entry point. Deeper guides live in this repository and are linked here:

🔼 Back to top



⚡ Quick Start

Create a value object by subclassing ValueObject[T] and adding validation hooks:

from value_object_pattern import ValueObject, validation


class Age(ValueObject[int]):
    @validation(order=0)
    def _ensure_value_is_integer(self, value: int) -> None:
        if type(value) is not int:
            raise TypeError('Age value must be an integer.')

    @validation(order=1)
    def _ensure_value_is_positive(self, value: int) -> None:
        if value <= 0:
            raise ValueError('Age value must be positive.')


age = Age(value=42)

print(age.value)
# >>> 42
print(repr(age))
# >>> Age(value=42)

Use reusable value objects when the package already provides the constraint:

from value_object_pattern.usables import NotEmptyStringValueObject, PositiveIntegerValueObject

name = NotEmptyStringValueObject(value='Ada')
quantity = PositiveIntegerValueObject(value=3)

🔼 Back to top



🧩 Core Ideas

Value objects in this package are designed around a few consistent rules:

  • They wrap exactly one value and expose it through .value.
  • They validate input during construction.
  • They can normalize input with @process hooks.
  • They reject attribute mutation after construction.
  • They compare by concrete class and wrapped value.
  • They can customize validation error context with title and parameter.
from value_object_pattern import process
from value_object_pattern.usables import StringValueObject


class LowerTrimmedName(StringValueObject):
    @process(order=0)
    def _trim(self, value: str) -> str:
        return value.strip()

    @process(order=1)
    def _lower(self, value: str) -> str:
        return value.lower()


name = LowerTrimmedName(value='  ADA  ')

assert name.value == 'ada'

🔼 Back to top



🤔 Why Value Objects?

Value objects make domain rules explicit. A plain str, int, or dict can carry almost anything, so every function that receives it has to remember what "valid" means. A value object gives that rule a name and enforces it at the point where the value enters the model.

Without a value object, the same rule tends to be repeated across services, controllers, tests, and serializers:

def register_user(email: str, age: int) -> None:
    if '@' not in email:
        raise ValueError('Invalid email.')

    if age <= 0:
        raise ValueError('Invalid age.')

With value objects, the signature communicates the expected shape and invalid values are rejected before the rest of the code depends on them:

from value_object_pattern.usables import PositiveIntegerValueObject
from value_object_pattern.usables.internet import EmailAddressValueObject


def register_user(email: EmailAddressValueObject, age: PositiveIntegerValueObject) -> None:
    assert '@' in email.value
    assert age.value > 0

This is useful when a value has a reusable meaning: email address, positive quantity, trimmed name, country code, URL, UUID, money identifier, or any project-specific concept such as TenantSlug, OrderLimit, or CustomerName.

Raw literals and primitives are still the right choice when the value is simple or intentionally exact:

  • Use raw primitives inside low-level calculations where no domain rule is being expressed.
  • Use hardcoded values for exact examples, snapshots, JSON, SQL, URLs, error messages, and public documentation output.
  • Use explicit boundary values for deliberate limits such as zero, one, minimum length, maximum length, empty strings, first date, last date, or a known invalid format.
  • Use .value when crossing into libraries, APIs, or storage layers that expect primitives.
from value_object_pattern.usables import PositiveIntegerValueObject

quantity = PositiveIntegerValueObject(value=10)

assert quantity.value == 10
assert quantity.value + 5 == 15

The practical rule is simple: use value objects for named domain constraints, and use primitives for exact literals, low-level operations, and boundary examples.

🔼 Back to top



📦 Core Models

Model Purpose
ValueObject[T] Base class for immutable validated single-value wrappers.
SecretValueObject Composition marker that redacts any value object's display in either inheritance order.
EnumerationValueObject[E] Stores enum members while accepting enum members or raw enum values.
UnionValueObject[T] Accepts and converts values that match a union annotation; supports subclass and inline construction.
BaseModel Adds representation, equality, copying, and primitive conversion for aggregate-like models.
ListValueObject[T] Immutable typed list wrapper; supports subclass and inline construction.
DictValueObject[K, V] Immutable typed dictionary wrapper; supports subclass and inline construction.
SequenceValueObject[T] Accepts non-scalar sequences and normalizes them to immutable tuples.
TupleValueObject[T] Strict homogeneous tuple wrapper.
MappingValueObject[K, V] Snapshots any mapping and exposes it read-only.
SetValueObject[T] Defensive typed set wrapper with immutable updates and set algebra.
FrozenSetValueObject[T] Typed frozen-set wrapper with immutable updates and set algebra.

See docs/usage/README.md for examples of each model.

Compose SecretValueObject with the typed value object that owns validation. The marker does not need a type argument and may appear before or after the value-object base:

from value_object_pattern import SecretValueObject
from value_object_pattern.usables import IntegerValueObject, StringValueObject


class SecretToken(SecretValueObject, StringValueObject):
    pass


class SecretPin(IntegerValueObject, SecretValueObject):
    pass


token = SecretToken(value='hidden-token')
pin = SecretPin(value=1234)

assert str(token) == '********'
assert str(pin) == '********'
assert token.value == 'hidden-token'
assert pin.value == 1234

Display redaction is not encryption: .value and primitive conversion still expose the raw stored value.

🔼 Back to top



✅ Reusable Value Objects

The package includes reusable validators for common shapes:

Category Examples
Primitives strings, bytes, booleans, integers, floats, finite numbers, None / not-None
String formats non-empty, trimmed, alpha, alphanumeric, lower/upper case, snake case, kebab case
Encoded strings hexadecimal/Base16, Base32, Base36, ambiguity-free Base56, Bitcoin Base58, standard Base64
Dates and time dates, datetimes, times, durations, their string forms, and timezones
Identifiers UUIDs, ULIDs, BCP 47 language tags, world codes, Spanish identifiers, and vehicle plates
Internet URLs, hosts, IP values, MAC addresses, ports, HTTP methods/statuses/media types, slugs, keys, and IMEI
Money ISO 4217 currency codes, IBANs, and credit-card values

See docs/catalog/README.md for import paths and category guidance.

🔼 Back to top



🔁 Primitive Conversion

BaseModel, typed collection value objects, and UnionValueObject can convert between primitive data and richer types.

from value_object_pattern import BaseModel
from value_object_pattern.usables import NotEmptyStringValueObject, PositiveIntegerValueObject


class User(BaseModel):
    def __init__(self, name: NotEmptyStringValueObject, age: PositiveIntegerValueObject) -> None:
        self.name = name
        self.age = age


user = User.from_primitives(primitives={'name': 'Ada', 'age': 42})

assert isinstance(user.name, NotEmptyStringValueObject)
assert user.to_primitives() == {'age': 42, 'name': 'Ada'}

More details are available in docs/conversion/README.md.

🔼 Back to top



🤝 Contributing

We love community help! Before you open an issue or pull request, please read:

Thank you for helping make 📦 Value Object Pattern package awesome! 🌟

🔼 Back to top



🔑 License

This project is licensed under the terms of the MIT license.

🔼 Back to top

Metadata

Release files for value-object-pattern 1.39.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for value-object-pattern 1.39.0
File Size Uploaded
value_object_pattern-1.39.0.tar.gz 131.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for value-object-pattern 1.39.0
File Interpreter ABI Platform
value_object_pattern-1.39.0-py3-none-any.whl Python 3 none any Details

Total release size: 398.9 kB

Release files / value_object_pattern-1.39.0.tar.gz

Download URL value_object_pattern-1.39.0.tar.gz
Size 131.1 kB
Tags Source
SHA-256 checksum
How to use checksums
d49a26e9449d49f8e7f7ca5ed9a1a71e68870e843427747f283fb16e51ee7061
BLAKE2b-256 checksum
How to use checksums
9bf1bb53aef8646872b2c3f298edfb22789c0c2466207a697a28a6844ea04e7a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / value_object_pattern-1.39.0-py3-none-any.whl

Download URL value_object_pattern-1.39.0-py3-none-any.whl
Size 267.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f03315bd11c2662f0650aac9ae8087e096a4b20f13d310bab1b4af64e2628c74
BLAKE2b-256 checksum
How to use checksums
64e78fac5faac4f89698eff0bacb412f2599c06e7ef8700acb4b096b72be08e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

1.39.1

2 release files

This release

1.39.0 This release

2 release files

1.38.0

2 release files

1.34.0

2 release files

1.33.1

2 release files

1.33.0

2 release files

1.32.0

2 release files

1.31.1

2 release files

1.31.0

2 release files

1.30.0

2 release files

1.29.2

2 release files

1.29.1

2 release files

1.29.0

2 release files

1.28.0

2 release files

1.27.0

2 release files

1.26.0

2 release files

1.25.0

2 release files

1.24.0

2 release files

1.22.1

2 release files

1.22.0

2 release files

1.21.0

2 release files

1.20.0

2 release files

1.19.0

2 release files

1.16.0

2 release files

1.15.1

2 release files

1.15.0

2 release files

1.12.0

2 release files

1.11.0

2 release files

1.10.0

2 release files

1.9.0

2 release files

1.8.0

2 release files

1.7.0

2 release files

1.6.0

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.0

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.10.0

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.5

2 release files

0.7.4

2 release files

0.7.3

2 release files

0.7.2

2 release files

0.7.1

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release 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