Composable specification pattern for Python
Project description
zspec
Composable specification pattern for Python 3.12+.
Documentation · PyPI · GitHub
Install
pip install zspec
Quick start
from dataclasses import dataclass
from zspec import Specification
@dataclass
class Product:
price: int
in_stock: bool
# Define rules as classes
class InStock(Specification[Product]):
def is_satisfied_by(self, p: Product) -> bool:
return p.in_stock
class MinPrice(Specification[Product]):
def __init__(self, threshold: int) -> None:
self.threshold = threshold
def is_satisfied_by(self, p: Product) -> bool:
return p.price >= self.threshold
# Compose with &, |, ^, ~
eligible = InStock() & MinPrice(100)
product = Product(price=200, in_stock=True)
assert eligible(product)
Or skip the class boilerplate:
# Field comparisons
spec = Specification[Product].matching(price__gte=100, in_stock=True)
# Lambda predicates
spec = Specification[Product].matching(
lambda p: p.price > 100,
lambda p: p.in_stock,
)
Why zspec?
| Composable | & | ^ ~ — build complex rules from simple ones without new classes |
| Zero dependencies | Standard library only. Optional extras for SQLAlchemy, Django, Polars, and Pandas |
| Type-safe | Generic Specification[T] preserves candidate types through composition |
| Database translators | One spec → SQL, MongoDB, Django Q, SQLAlchemy, Polars, or Pandas expression |
| Serializable | to_dict() / from_dict() — store rules in JSON configs or databases |
| Debuggable | explain() prints a PASS / FAIL tree for every node |
Filtering collections
passed = list(eligible.filter(products)) # lazy generator
failed = list(eligible.reject(products)) # inverse
passed, failed = eligible.partition(products)
count = eligible.count(products)
Debugging
from zspec import explain
print(explain(eligible, product))
# AND FAIL
# ├── InStock PASS
# └── price >= 100 FAIL
Serialization
from zspec import to_dict, from_dict, registered
@registered
class InStock(Specification[Product]):
...
# Save
json.dump(to_dict(InStock() & MinPrice(100)), f)
# Load — @registered specs are auto-discovered
spec = from_dict(json.load(f))
Translators
One spec — query any backend:
# SQL
MySql().translate(eligible) # SqlFragment("price >= %s AND in_stock", (100, True))
# MongoDB
MyMongo().translate(eligible) # {"$and": [{...}, {...}]}
# Django
MyDjango().translate(eligible) # Q(price__gte=100) & Q(in_stock=True)
# SQLAlchemy
MySA().translate(eligible) # ColumnElement[bool]
# Polars
MyPolars().translate(eligible) # pl.Expr
# Pandas
MyPandas().translate(eligible) # query string
License
MIT
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
zspec-1.6.0.tar.gz
(10.7 kB
view details)
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
zspec-1.6.0-py3-none-any.whl
(14.7 kB
view details)
File details
Details for the file zspec-1.6.0.tar.gz.
File metadata
- Download URL: zspec-1.6.0.tar.gz
- Upload date:
- Size: 10.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a56787b8b881879f40d49685dd02c0b9f2bbcf3e50653662c356c1a5ce1668f5
|
|
| MD5 |
6123b603ff0199a89f3049bd3f98043f
|
|
| BLAKE2b-256 |
9835af7428bfe0ab7f499f0488ae27cb250b895cd359bac990552a2fc13bfaf1
|
File details
Details for the file zspec-1.6.0-py3-none-any.whl.
File metadata
- Download URL: zspec-1.6.0-py3-none-any.whl
- Upload date:
- Size: 14.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7ac01e20e7e5e1d2b7691945cd2a0e7a5ac7d08336e4140e03b61afd9a96e2f
|
|
| MD5 |
5642183e0d0113754e03a18722eda203
|
|
| BLAKE2b-256 |
0407288d8e9fdcc682c3594cd8e3f30e9f8042b3980291cc0ffbfd8910e157e4
|