📦 Value Object Pattern
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
- 📥 Installation
- 📚 Documentation
- ⚡ Quick Start
- 🧩 Core Ideas
- 🤔 Why Value Objects?
- 📦 Core Models
- ✅ Reusable Value Objects
- 🔁 Primitive Conversion
- 🤝 Contributing
- 🔑 License
📥 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.
📚 Documentation
The root README is the entry point. Deeper guides live in this repository and are linked here:
docs/README.md: Documentation hub.docs/usage/README.md: Custom value objects, validators, processors, and model composition.docs/catalog/README.md: Feature map by reusable value-object category.- Catalog details:
primitives,dates,identifiers,internet, andmoney. docs/conversion/README.md: Primitive conversion and nested model behavior.AI Skill: Installable skill package that teaches AI agents how to use Value Object Pattern.
⚡ 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)
🧩 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
@processhooks. - They reject attribute mutation after construction.
- They compare by concrete class and wrapped value.
- They can customize validation error context with
titleandparameter.
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'
🤔 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
.valuewhen 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.
📦 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.
✅ 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.
🔁 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.
🤝 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! 🌟
🔑 License
This project is licensed under the terms of the MIT license.
Metadata
Release files for value-object-pattern 1.39.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| value_object_pattern-1.39.1.tar.gz | 131.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| value_object_pattern-1.39.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 398.9 kB
Release files / value_object_pattern-1.39.1.tar.gz
| Download URL | value_object_pattern-1.39.1.tar.gz |
|---|---|
| Size | 131.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3de7471221e479e8946791ceaa42cb9065512dd8e3534bb0dc3243002ab439b8
|
|
BLAKE2b-256 checksum How to use checksums |
eded82aea4bdefdc5f09468512f69d677cef67d937df53d18b18ed33d682d38f
|
| 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 logRelease files / value_object_pattern-1.39.1-py3-none-any.whl
| Download URL | value_object_pattern-1.39.1-py3-none-any.whl |
|---|---|
| Size | 267.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
7b93ec1420dea6f18d8bad5cfa0d19298a39a7025c16156e8c66837521469352
|
|
BLAKE2b-256 checksum How to use checksums |
2515e023405ac9cd577822c5b3d67419c14c392ca6b8969136b9774b1cf8616b
|
| 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