The Value Object Pattern is a Python package that streamlines the creation and management of value objects in your projects.
Project description
📦 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.
This project's DeepWiki documentation is also available for generated repository navigation.
⚡ 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. |
EnumerationValueObject[E] |
Stores enum members while accepting enum members or raw enum values. |
UnionValueObject[T] |
Accepts and converts values that match a union annotation. |
BaseModel |
Adds representation, equality, copying, and primitive conversion for aggregate-like models. |
ListValueObject[T] |
Immutable typed list wrapper with helper methods that return new instances. |
DictValueObject[K, V] |
Immutable typed dictionary wrapper with helper methods that return new instances. |
See docs/usage/README.md for examples of each model.
✅ Reusable Value Objects
The package includes reusable validators for common shapes:
| Category | Examples |
|---|---|
| Primitives | strings, bytes, booleans, integers, floats, None / not-None |
| String formats | non-empty, trimmed, alpha, alphanumeric, lower/upper case, snake case, kebab case, secret strings |
| Dates | date, datetime, date strings, datetime strings, timezone objects, timezone names |
| Identifiers | UUIDs and UUID strings, world codes, Spanish identifiers and vehicle plates |
| Internet | URLs, hosts, domains, ports, emails, IP addresses, networks, MAC addresses, slugs, keys |
| Money | IBANs and credit card values |
See docs/catalog/README.md for import paths and category guidance.
🔁 Primitive Conversion
BaseModel, ListValueObject, DictValueObject, 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file value_object_pattern-1.30.0.tar.gz.
File metadata
- Download URL: value_object_pattern-1.30.0.tar.gz
- Upload date:
- Size: 84.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
818961b70ccb4f9cdaa0cce7e98a78905da1671ed872ff05568f9aa66af8b215
|
|
| MD5 |
e3c74e6af6267964f17bd10a3c35757b
|
|
| BLAKE2b-256 |
c77fad5283ca594a251bea7f4d4645f094d69748d256dd1aeafea2a37683c381
|
Provenance
The following attestation bundles were made for value_object_pattern-1.30.0.tar.gz:
Publisher:
ci.yaml on adriamontoto/value-object-pattern
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
value_object_pattern-1.30.0.tar.gz -
Subject digest:
818961b70ccb4f9cdaa0cce7e98a78905da1671ed872ff05568f9aa66af8b215 - Sigstore transparency entry: 1853325449
- Sigstore integration time:
-
Permalink:
adriamontoto/value-object-pattern@1dfb2d7164211254f00325ce67f7e02beae66008 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/adriamontoto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yaml@1dfb2d7164211254f00325ce67f7e02beae66008 -
Trigger Event:
push
-
Statement type:
File details
Details for the file value_object_pattern-1.30.0-py3-none-any.whl.
File metadata
- Download URL: value_object_pattern-1.30.0-py3-none-any.whl
- Upload date:
- Size: 205.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
362e0c7aa0ca037c28d2af2a10a5ec3307f87968f37537944018477f116d1c78
|
|
| MD5 |
d7c302454e7889f4c59a008c4a07e3b5
|
|
| BLAKE2b-256 |
9d34bd6c7e39a391b83fd0fe460a6dc0523fb7c45eb734151e582ef740842239
|
Provenance
The following attestation bundles were made for value_object_pattern-1.30.0-py3-none-any.whl:
Publisher:
ci.yaml on adriamontoto/value-object-pattern
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
value_object_pattern-1.30.0-py3-none-any.whl -
Subject digest:
362e0c7aa0ca037c28d2af2a10a5ec3307f87968f37537944018477f116d1c78 - Sigstore transparency entry: 1853325460
- Sigstore integration time:
-
Permalink:
adriamontoto/value-object-pattern@1dfb2d7164211254f00325ce67f7e02beae66008 -
Branch / Tag:
refs/heads/master - Owner: https://github.com/adriamontoto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yaml@1dfb2d7164211254f00325ce67f7e02beae66008 -
Trigger Event:
push
-
Statement type: