Skip to main content

Composable specification pattern for Python

Project description

zspec

PyPI Python CI Coverage License

Composable specification pattern for Python 3.14+.

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, and Polars
Type-safe Generic Specification[T] preserves candidate types through composition
Database translators One spec → SQL, MongoDB, Django Q, SQLAlchemy, or Polars 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

License

MIT

Project details


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.5.0.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zspec-1.5.0-py3-none-any.whl (14.6 kB view details)

Uploaded Python 3

File details

Details for the file zspec-1.5.0.tar.gz.

File metadata

  • Download URL: zspec-1.5.0.tar.gz
  • Upload date:
  • Size: 10.6 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

Hashes for zspec-1.5.0.tar.gz
Algorithm Hash digest
SHA256 dcd69756efdf7a7100b118b7e060260fbf570ec4eff6def7d651b1d577c21cde
MD5 992864f5e6f04a2bb92fed438ff6046d
BLAKE2b-256 312a6ac97f6f6a0cf50e9726cc4d6ce99e98622e4fadaa17380f376989fde467

See more details on using hashes here.

File details

Details for the file zspec-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: zspec-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 14.6 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

Hashes for zspec-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 39890e25fff6614fa9dc2783bed93ce2ca6755a1208e69c745fe1acda1be4ab8
MD5 f70362729a6f276d9b34414615a43062
BLAKE2b-256 8165286fa91dacb39298fefa67b3f3b237b6c203da2943fcf868604df5fcb80b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page