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.4.0.tar.gz (10.4 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.4.0-py3-none-any.whl (14.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: zspec-1.4.0.tar.gz
  • Upload date:
  • Size: 10.4 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.4.0.tar.gz
Algorithm Hash digest
SHA256 b61f15595fac0d01de8a50aa83cdad93812a8e94b29cf3431b1ae31bed8e708b
MD5 b926e59ecdebedef9b8cf2f5d25eeaa8
BLAKE2b-256 ea95b4f1e1f50f7994fa9e98b554fa1893568f04cdfa365623ad99b0ec7b2174

See more details on using hashes here.

File details

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

File metadata

  • Download URL: zspec-1.4.0-py3-none-any.whl
  • Upload date:
  • Size: 14.0 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.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 03fa304e5ac0c70bcceb23caf7380b4b04c7276a711e6b61d3b456636a078f6e
MD5 f0abb35722f06ebddbf63147946f4b1b
BLAKE2b-256 eeea9b954c704d42ba0a323b2898d11100a20c62efbf634f7b0565e116a67c35

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