Lightweight Python library extending dataclasses with serialization, deserialization, validation, and mixins for comparison, hashing, and immutable updates.
Project description
Corbel
Corbel is a Python dataclass extension library providing mixins and utilities for:
- Validation
- Comparison and hashing
- Copying and updating
- Serialization to/from dicts and JSON
- Property-level metadata (
@corbel_property) - Protocol-based hooks for validation, serialization, and deserialization
Installation
pip install corbel
Core Mixins
1. Corbel
The base mixin that provides:
- Cached
asdictresults - Field and property introspection
- Hook methods for updates and validation
from corbel import dataclass
from corbel import Corbel, field
@dataclass
class Base(Corbel):
x: int = field()
y: str = field()
inst = Base(1, "hello")
print(inst.asdict()) # {'x': 1, 'y': 'hello'}
2. Serializable
Provides to_dict, from_dict, to_json, from_json for dataclasses.
- Supports nested dataclasses
- Optional wrapper key for JSON (
__json_wrapper__) - Configurable inclusion rules (
__inclusion__)
from corbel import dataclass
from corbel import Serializable, field, Include
@dataclass
class User(Serializable):
id: int = field()
name: str = field()
email: str | None = field(default=None)
user = User(1, "Alice")
print(user.to_dict()) # {'id': 1, 'name': 'Alice', 'email': None}
alice = User.from_dict({"id": 1, "name": "Alice", "email": None})
# JSON with wrapper
print(user.to_json(wrapper="user")) # {"user": {"id":1,"name":"Alice","email":null}}
alice = User.from_json('{"user": {"id":1,"name":"Alice","email":null}', wrapper="user")
# Custom class-level JSON wrapper
@dataclass
class WrappedUser(Serializable):
__json_wrapper__ = "account"
id: int = field()
name: str = field()
u = WrappedUser(5, "Bob")
print(u.to_json()) # {"account": {"id":5,"name":"Bob"}}
# Using inclusion rules
@dataclass
class PartialUser(Serializable):
__inclusion__ = Include.NON_NONE
id: int = field()
name: str = field()
email: str | None = field(default=None)
pu = PartialUser(1, "Alice")
print(pu.to_dict()) # {'id': 1, 'name': 'Alice'} # email omitted
3. Updatable
Provides immutable-style updates:
copy(): shallow copyupdate(**kwargs): returns a new instance with updated fieldsbatch_update(): context manager to temporarily disable validation
from corbel import dataclass
from corbel import Updatable, field
@dataclass
class Point(Updatable):
x: int = field()
y: int = field()
p1 = Point(1, 2)
p2 = p1.update(x=10) # new instance
print(p1.asdict()) # {'x': 1, 'y': 2}
print(p2.asdict()) # {'x': 10, 'y': 2}
# batch update
with p2.batch_update() as temp:
temp.x = 20
temp.y = 30
print(p2.asdict()) # {'x': 10, 'y': 2}, p2 unchanged
4. Validated
Automatically validates fields on initialization and update:
- Define a
validatorinfield()metadata - Supports
allow_none=True - Raises
ValidationErroron failure
from corbel import dataclass
from corbel import Validated, field, ValidationError
def positive(value: int) -> bool:
return value > 0
@dataclass
class BankAccount(Validated):
balance: int = field(validator=positive)
try:
acct = BankAccount(-10) # raises ValidationError
except ValidationError as e:
print(e)
acct = BankAccount(100)
acct.balance = -50 # raises ValidationError
5. Hashable
Caches a hash based on dataclass fields:
- Automatically invalidates on field update
- Suitable for dict keys and set members
from corbel import dataclass
from corbel import Hashable, field
@dataclass
class Coord(Hashable):
x: int = field()
y: int = field()
c1 = Coord(1, 2)
c2 = Coord(1, 2)
print(hash(c1) == hash(c2)) # True
c1.x = 3
print(hash(c1) == hash(c2)) # False
6. Comparable
Provides <, <=, >, >=, == based on field values:
- Lexicographic comparison of fields
- Supports total ordering
from corbel import dataclass
from corbel import Comparable, field
@dataclass
class Version(Comparable):
major: int = field()
minor: int = field()
v1 = Version(1, 0)
v2 = Version(1, 1)
print(v1 < v2) # True
print(v1 == v2) # False
7. @corbel_property
Custom property decorator supporting:
validatorserializer/deserializerallow_none/ignore
from corbel import dataclass
from corbel import Corbel, corbel_property, field
def positive(x: int) -> bool:
return x > 0
@dataclass
class Example(Corbel):
_value: int = field()
@corbel_property(validator=lambda v: positive(v))
def value(self) -> int:
return self._value
@value.setter
def value(self, val: int) -> None:
self._value = val
ex = Example(5)
print(ex.value) # 5
ex.value = 10 # OK
# ex.value = -1 # Raises ValueError
8. Protocol Examples
ValidatorProtocol
from typing import Any
def positive_validator(value: int) -> bool:
return value > 0
print(positive_validator(5)) # True
print(positive_validator(-1)) # False
SerializerProtocol
from typing import Any
def uppercase_serializer(value: Any) -> Any:
if isinstance(value, str):
return value.upper()
return value
print(uppercase_serializer("hello")) # "HELLO"
DeserializerProtocol
from typing import Any
def deserialize_int(value: Any, type_hint: int) -> int:
if type_hint == int and isinstance(value, str):
return int(value)
return value
print(deserialize_int("42", int)) # 42
9. Combining Mixins
Mixins can be combined for full-featured dataclasses:
from corbel import dataclass
from corbel import Serializable, Updatable, Validated, Hashable, Comparable, field, corbel_property
@dataclass
class Product(Serializable, Updatable, Validated, Hashable, Comparable):
name: str = field()
price: float = field()
@corbel_property()
def discounted_price(self) -> float:
return self.price * 0.9
prod = Product("Widget", 100)
prod2 = prod.update(price=120)
print(prod.to_dict()) # {'name': 'Widget', 'price': 100}
print(prod2.discounted_price) # 108.0
Utilities
asdict(obj, include_private=False): convert instance to dictfield(**kwargs): wrapper for dataclass fields with Corbel metadatafields(obj): returns dataclass fieldsIncludeenum:ALWAYS,NON_NONE,NON_EMPTY,NON_DEFAULT- Exceptions:
ValidationError,DeserializeError,InclusionError,CorbelError - Class-level options for Serializable:
__json_wrapper__– wrap the JSON output under a key__inclusion__– control which fields are included
License
MIT License. See 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 corbel-0.1.3.tar.gz.
File metadata
- Download URL: corbel-0.1.3.tar.gz
- Upload date:
- Size: 28.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.2 CPython/3.11.5 Darwin/25.0.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
033a3a3a294af74ff9ce7e61832141ab8d8b883dabf1326122e29d1b83d27379
|
|
| MD5 |
e07eef1f3143f482a25937619aa0717b
|
|
| BLAKE2b-256 |
b67acd8c4d89bd2fdfe9608d4e60801861416914ff76291fa47b519ecfc2ec5b
|
File details
Details for the file corbel-0.1.3-py3-none-any.whl.
File metadata
- Download URL: corbel-0.1.3-py3-none-any.whl
- Upload date:
- Size: 45.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.8.2 CPython/3.11.5 Darwin/25.0.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e645a4c12e7b5969b530ad85c547da9910c686763b46c1bda26c30c5c24b77f2
|
|
| MD5 |
a614646aa69bd8fc56e315a986097d77
|
|
| BLAKE2b-256 |
fd2a2de3ce8537f788d6e090bbe0bf2e51f880068cde0ef0a7e6667f3318c244
|