A compact predicate DSL for matching criteria against any object
Project description
koalify
A compact predicate DSL for matching criteria against any Python object. Zero runtime dependencies.
Installation
pip install koalify
Quick Start
from koalify import F, all_of, any_of
# Build rules with Python operators
is_eligible = (
(F.status == "active")
& (F.age >= 18)
& (F.role.in_("admin", "moderator", "editor"))
& F.score.between(50, 100)
)
# Evaluate against any object with attributes
is_eligible(user) # True / False
# Nested fields
lives_in_london = F.address.city == "London"
# Compose with OR / NOT
can_access = is_eligible | (lives_in_london & ~(F.status == "banned"))
# Dynamic composition from a list
conditions = [F.status == "active", F.age >= 18]
rule = all_of(*conditions)
Examples
Dataclasses
from dataclasses import dataclass
from koalify import F, all_of
@dataclass
class Order:
product: str
quantity: int
price: float
fulfilled: bool
needs_review = (F.quantity > 100) & (F.price >= 500) & (F.fulfilled == False)
order = Order(product="Widget", quantity=200, price=750.0, fulfilled=False)
needs_review(order) # True
Pydantic
from pydantic import BaseModel
from koalify import F, any_of
class Address(BaseModel):
city: str
country: str
class Customer(BaseModel):
name: str
tier: str
address: Address
is_priority = (F.tier.in_("gold", "platinum")) | (F.address.country == "US")
customer = Customer(name="Alice", tier="gold", address=Address(city="London", country="UK"))
is_priority(customer) # True
Item access (lists, dicts)
from dataclasses import dataclass
from koalify import F
@dataclass
class Event:
name: str
tags: list[str]
metadata: dict[str, str]
event = Event(name="deploy", tags=["prod", "urgent"], metadata={"region": "eu-west-1"})
# List indexing
(F.tags[0] == "prod")(event) # True
# Dict key access
(F.metadata["region"] == "eu-west-1")(event) # True
# Mix with attribute access and composition
is_urgent_prod = (F.tags[0] == "prod") & (F.tags[1] == "urgent")
is_urgent_prod(event) # True
Dynamic rule composition
from koalify import F, all_of
def build_filter(min_age: int | None = None, status: str | None = None, roles: set[str] | None = None):
criteria = []
if min_age is not None:
criteria.append(F.age >= min_age)
if status is not None:
criteria.append(F.status == status)
if roles is not None:
criteria.append(F.role.in_(*roles))
return all_of(*criteria) if criteria else lambda _: True
user_filter = build_filter(min_age=18, roles={"admin", "editor"})
Implication (AND-of-implications)
implies(antecedent, consequent) encodes "if A then B" — vacuously true when the
antecedent does not hold. Combine with all_of(...) to express a table of
conditional constraints, where each row contributes a clause that only "bites"
when its selector matches.
from koalify import F, all_of, implies
# Per-(plan, kind) upload size limit. plan and kind select which row applies;
# size_mb is the value being constrained.
cases = [
{"plan": "free", "kind": "image", "size_mb": 5},
{"plan": "pro", "kind": "video", "size_mb": 500},
]
within_limits = all_of(*[
implies(
(F.plan == c["plan"]) & (F.kind == c["kind"]),
F.size_mb < c["size_mb"],
)
for c in cases
])
Uploads whose (plan, kind) matches no row pass vacuously. Swap all_of for
any_of(... & ...) (DNF) if you want unmatched uploads to fail instead.
Serialization
Criteria expose to_dict() for JSON-safe export, and load() rebuilds a
criterion from a dict or JSON string. This lets rules live in config files,
databases, or be exchanged across services.
import json
from koalify import F, load
rule = (F.status == "active") & (F.age >= 18) & F.role.in_("admin", "editor")
payload = rule.to_dict()
# {
# "type": "and",
# "criteria": [
# {"type": "eq", "field": ["status"], "value": "active"},
# {"type": "ge", "field": ["age"], "value": 18},
# {"type": "in", "field": ["role"], "values": ["admin", "editor"]},
# ],
# }
restored = load(payload) # from dict
restored = load(json.dumps(payload)) # or from JSON string
restored(user) # evaluates identically
Field paths serialize as lists — attribute segments as strings, item-access
segments as {"key": ...}:
(F.address.city == "London").to_dict()
# {"type": "eq", "field": ["address", "city"], "value": "London"}
(F.tags[0] == "vip").to_dict()
# {"type": "eq", "field": ["tags", {"key": 0}], "value": "vip"}
implies(...) round-trips as its Or(Not(...), ...) expansion — no special
casing needed.
API
| Symbol | Description |
|---|---|
F.field |
Reference a field (supports nesting: F.a.b.c and indexing: F.a[0], F.a["k"]) |
== != > >= < <= |
Comparison operators on FieldRef |
.in_(*values) |
Set membership |
.between(lo, hi) |
Inclusive range check |
& |
AND (flattens nested ANDs) |
| |
OR (flattens nested ORs) |
~ |
NOT |
all_of(*criteria) |
AND from a list |
any_of(*criteria) |
OR from a list |
implies(a, b) |
Logical implication (~a | b) |
criterion.to_dict() |
Serialize to a JSON-safe dict |
load(data) |
Rebuild a criterion from a dict or JSON string |
How It Works
F.field_namereturns aFieldRef— a lightweight path descriptor- Comparison operators (
==,>,.in_(), etc.) produceCriterionobjects - Criteria compose with
&,|, and~(flattening nested groups automatically) - Calling a criterion resolves field values at runtime via
getattrand[]
Works with dataclasses, Pydantic models, namedtuples, or any object with attributes.
Item access (F.tags[0], F.data["key"]) delegates to the resolved value's __getitem__, so standard IndexError / KeyError exceptions propagate naturally.
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
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 koalify-1.4.0.tar.gz.
File metadata
- Download URL: koalify-1.4.0.tar.gz
- Upload date:
- Size: 6.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.12.13 Linux/6.17.0-1015-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1c4658237f789f4b78f9996694ed41bc18a2f8cb5f0f33ccf0f3f12a394921f5
|
|
| MD5 |
a8adfa4186d897578722381e6b1c3a8c
|
|
| BLAKE2b-256 |
e1b332988c63e36ba11a4e2e1236e499d34625545dcbf274689c5b228f442221
|
File details
Details for the file koalify-1.4.0-py3-none-any.whl.
File metadata
- Download URL: koalify-1.4.0-py3-none-any.whl
- Upload date:
- Size: 9.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.4.1 CPython/3.12.13 Linux/6.17.0-1015-azure
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
104e55237d5b3a97392e3bc6921ca1540ef20553ae6147dca53416be5ec8d63a
|
|
| MD5 |
42662f50f7f9581824e38d2a68ac7540
|
|
| BLAKE2b-256 |
e82c5b7f311b940aaa27f8538373dc77757eece06c6d0e6ace261b08885b5480
|