CLIPS Python bindings via CFFI
Project description
clipspyx
Python bindings and DSL for CLIPS, the production rule engine. Write rules as Python classes, run them with an async goal handler loop, trace fact derivation chains, and visualize your rule network as D2 diagrams.
Originally forked from clipspy, clipspyx adds a Python-native DSL, async backward chaining, fact tracing, and full CLIPS 7.0 support while keeping the low-level CFFI API available for everything else.
Quick start
pip install clipspyx # or: uv add clipspyx
Low-level API
from clipspyx import Environment
env = Environment()
env.build('(defrule hello (initial-fact) => (println "Hello CLIPS!"))')
env.reset()
env.run()
Python DSL
Define CLIPS templates and rules as Python classes:
from clipspyx import Environment
from clipspyx.dsl import Template, Rule, Multi
class Employee(Template):
name: str
title: str
years: int = 0
class Skill(Template):
name: str
proficiency: int = 1
class Project(Template):
name: str
required_skill: str
min_proficiency: int = 1
class AssignProject(Rule):
"""Match employees to projects by skill."""
e = Employee(name=name)
Skill(name=skill, proficiency=prof)
Project(name=proj, required_skill=skill, min_proficiency=min_prof)
prof >= min_prof
def __action__(self):
print(f"Assign {self.name} to project {self.proj}")
env = Environment()
NewEmployee = env.define(Employee)
NewSkill = env.define(Skill)
NewProject = env.define(Project)
env.define(AssignProject)
env.reset()
NewEmployee(name="Alice", title="Engineer", years=5)
NewSkill(name="Python", proficiency=4)
NewProject(name="Atlas", required_skill="Python", min_proficiency=3)
env.run()
# => Assign Alice to project Atlas
The DSL supports the full range of CLIPS conditional elements:
| Python syntax | CLIPS CE |
|---|---|
p = Person(name=name) |
Assigned pattern |
Person(age=_) |
Wildcard |
Person(age=25) |
Literal constraint |
Person(name=not "Bob") |
Field negation |
Person(age=25 or 30) |
Field or |
age >= 18 |
Test CE |
~Person(name="Bob") |
Negation |
exists(Person()) |
Exists CE |
forall(Person(name=n), Badge(owner=n)) |
Forall CE |
logical(Person(name=n)) |
Logical CE |
Rules can declare effects without a Python bridge function:
class DoubleInput(Rule):
i = Input(value=v)
asserts(Output(value=v * 2))
class Cleanup(Rule):
p = Temp(flag=Symbol("done"))
retracts(p)
Fact tracing
Track which rules produced which facts:
env.enable_tracing()
env.reset()
# ... assert facts and run ...
for firing in env.find_template('RuleFiring').facts():
print(f"{firing['rule']}: {firing['inputs']} -> {firing['outputs']}")
Each RuleFiring fact records the rule name, the input facts that matched,
and the output facts that were asserted.
Visualization
Generate D2 diagrams of your rule network:
# Print D2 text
print(env.visualize(group_by_kind=True))
# Render to SVG (requires d2 CLI)
env.visualize(output="diagram.svg", group_by_kind=True)
Templates, rules, pattern edges, fact-address references, and effect edges
are all represented. See examples/hr_system.py for a full example.
Async goal handlers (CLIPS 7.0)
CLIPS 7.0 backward chaining generates goals when a rule needs a fact that doesn't exist yet. clipspyx dispatches these goals to Python async handlers, enabling rules that wait on timers, HTTP calls, or any async I/O:
import asyncio
from clipspyx import Environment
from clipspyx.dsl import Template, Rule, TimerEvent, AFTER
from clipspyx.values import Symbol
class Alert(Template):
msg: str
class DelayedAlert(Rule):
te = TimerEvent(kind=AFTER, name=Symbol("alarm"), seconds=2.0)
asserts(Alert(msg=Symbol("time-is-up")))
env = Environment()
env.enable_goal_handlers()
env.define(Alert)
env.define(DelayedAlert)
env.reset()
asyncio.run(env.async_run())
Built-in TimerEvent supports one-shot (AFTER), absolute (AT), and
periodic (EVERY) timers. Register custom handlers for your own goal
templates:
async def query_handler(goal, env):
result = await fetch_from_service(goal['service'], goal['query'])
env.find_template('QueryResult').assert_fact(result=result)
env.register_goal_handler('query-goal', query_handler)
CLIPS 7.0 features
When built against CLIPS 7.0x, clipspyx also supports:
- Template inheritance:
(deftemplate car (is-a vehicle) (slot doors)) - Certainty factors:
(deftemplate sensor (is-a CFD) (slot reading))with CF propagation through rule chains - Deftables: static lookup tables accessible from rules and Python
- Backward chaining: goal generation with
(goal ...)patterns update_slots: selective rete re-evaluation (only rules matching the changed slot)
Building from source
clipspyx compiles CLIPS source directly into the CFFI extension. No separate
libclips build step.
Prerequisites
- Python 3.10+
- C compiler (gcc/clang on Linux/macOS, MSVC on Windows)
- CLIPS source (auto-checked out from orphan branch, or provide your own)
Install for development
# Install with 64x backend (CLIPS 6.4x)
uv sync --extra 64x
# Install with 70x backend (CLIPS 7.0x)
uv sync --extra 70x
Switching backends
The 64x and 70x backends conflict with each other. Always use uv sync to switch:
uv sync --extra 64x # switch to 6.4x
uv sync --extra 70x # switch to 7.0x
uv run --extra does not remove the previously installed conflicting backend.
Only uv sync --extra <variant> correctly handles the switch.
CLIPS source override
CLIPS_SOURCE_DIR=/path/to/clips/core uv sync --extra 64x
Build distributable wheels
uv run scripts/build-backend.py 64x # builds clipspyx-ffi + clipspyx-ffi-64x wheels
uv run scripts/build-backend.py 70x # builds clipspyx-ffi + clipspyx-ffi-70x wheels
Testing
uv run pytest tests/ -v
License
BSD-3-Clause. See LICENSE.txt.
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 clipspyx-0.10.3.tar.gz.
File metadata
- Download URL: clipspyx-0.10.3.tar.gz
- Upload date:
- Size: 126.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c0df9ee1a45cfa6ddd49d4dc523be36f086f231a3ebf18a848144eb27a65bae1
|
|
| MD5 |
e2036d6600851a69189d4e0e17f35974
|
|
| BLAKE2b-256 |
5b9f47a9e2189b48470c6915667ca2e7b4616d4541730f5e01a0f3abd5d0a806
|
Provenance
The following attestation bundles were made for clipspyx-0.10.3.tar.gz:
Publisher:
wheels.yml on inferal-oss/clipspyx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clipspyx-0.10.3.tar.gz -
Subject digest:
c0df9ee1a45cfa6ddd49d4dc523be36f086f231a3ebf18a848144eb27a65bae1 - Sigstore transparency entry: 1194647393
- Sigstore integration time:
-
Permalink:
inferal-oss/clipspyx@77562541ba8bf19f39a28ae2cbaa297a9fac1149 -
Branch / Tag:
refs/tags/v0.10.3 - Owner: https://github.com/inferal-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@77562541ba8bf19f39a28ae2cbaa297a9fac1149 -
Trigger Event:
push
-
Statement type:
File details
Details for the file clipspyx-0.10.3-py3-none-any.whl.
File metadata
- Download URL: clipspyx-0.10.3-py3-none-any.whl
- Upload date:
- Size: 82.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
88cd722665af621de67710818b291164201cbe41c7cfe11ecf2d5c89ead2f53a
|
|
| MD5 |
83cad281f99897452c6c5c07d85db534
|
|
| BLAKE2b-256 |
3526d17a9972858952312b007c378993e7697975b8ec81b0e3221c255a8f0441
|
Provenance
The following attestation bundles were made for clipspyx-0.10.3-py3-none-any.whl:
Publisher:
wheels.yml on inferal-oss/clipspyx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
clipspyx-0.10.3-py3-none-any.whl -
Subject digest:
88cd722665af621de67710818b291164201cbe41c7cfe11ecf2d5c89ead2f53a - Sigstore transparency entry: 1194647737
- Sigstore integration time:
-
Permalink:
inferal-oss/clipspyx@77562541ba8bf19f39a28ae2cbaa297a9fac1149 -
Branch / Tag:
refs/tags/v0.10.3 - Owner: https://github.com/inferal-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@77562541ba8bf19f39a28ae2cbaa297a9fac1149 -
Trigger Event:
push
-
Statement type: