Build, serve and inspect decision pipelines
Project description
Decider
Decider is a Python framework for building, serving, and inspecting decision pipelines as versioned, deployable micro-services. Define pipelines from plain Python functions, compose them with | and &, save them as versioned JSON configs, and serve them over HTTP — all with a single consistent API.
Table of Contents
Introduction
Decider is built around a few core ideas:
- Functions as nodes — plain Python functions that accept and return
polars.Exprare wired into executable DAGs automatically, with no decorators or registries required. - Composable pipelines — modules chain with
|(sequential) or merge with&(parallel union), making it easy to build complex pipelines from simple parts. - Versioned configs — every pipeline is serialisable to JSON. Configs are versioned, loadable by the server at startup, and hot-swappable without redeployment.
- Pluggable extensions — new module types are registered into a discriminated union at runtime, so the server always knows how to reconstruct any module from its config.
Installation
# Core library and CLI
pip install decider
# With serving dependencies
pip install "decider[serve-starlette]" # uvicorn + starlette
pip install "decider[serve-sanic]" # sanic
# With the interactive graph visualiser
pip install "decider[visualise]"
# Everything
pip install "decider[all]"
With uv (recommended):
git clone https://github.com/capitecbankltd/dsp_north-polrs.git
cd dsp_north-polrs
uv sync --all-extras
Concepts
Modules
A module is the basic unit of computation. The simplest way to create one is generate_from_functions, which turns plain functions into an executable module:
- Function name → output column —
def dti_ratio(...)produces adti_ratiocolumn. - Parameter name → input column or sibling output — parameters are resolved from the input DataFrame or from the output of another function in the same module.
configparameter → Pydantic model injection — declareconfig: MyConfigand the config fields are promoted onto the module itself.
Pipelines
Modules compose with two operators:
|— sequential:step_a | step_b | step_cpasses each step's output as the next step's input.&— union:module_a & module_bmerges both modules into a single compilation pass, computing all columns in one frame scan.
Versioned Configs
Every module can be saved to a versioned JSON config and reconstructed from it:
await module.asave("main", config_manager)
await config_manager.save_version(overwrite=True)
The server reads the latest version on startup and watches for updates.
Extensions
Custom module types are registered with register_graph_module and auto-discovered by initialize_decider from an extension_path directory. This keeps the core library small while allowing domain-specific modules to be developed and shipped independently.
Usage Examples
Your first module
import polars as pl
from decider.modules.functional import generate_from_functions
def dti_ratio(debt: pl.Expr, income: pl.Expr) -> pl.Expr:
return debt / income
def credit_score(dti_ratio: pl.Expr) -> pl.Expr:
return pl.lit(800) - dti_ratio * 200
Scorer = generate_from_functions("credit_scorer", dti_ratio, credit_score)
scorer = Scorer(name="scorer")
df = pl.DataFrame({"debt": [25_000.0], "income": [50_000.0]})
result = scorer({"input": df})
# shape: (1, 4) — debt, income, dti_ratio, credit_score
Config injection
from pydantic import BaseModel
class ScorerConfig(BaseModel):
dti_weight: float = 200.0
score_base: float = 800.0
def credit_score(dti_ratio: pl.Expr, config: ScorerConfig) -> pl.Expr:
return pl.lit(config.score_base) - dti_ratio * config.dti_weight
Scorer = generate_from_functions("credit_scorer", dti_ratio, credit_score)
scorer = Scorer(name="scorer", dti_weight=150.0) # config fields on the module
Sequential pipeline
features = FeatureModule(name="features")
scorer = ScorerModule(name="scorer")
flags = FlagModule(name="flags")
pipeline = features | scorer | flags
result = pipeline({"input": df})
Join then score
from decider.modules.primitives.join import JoinModule
join = JoinModule(name="enrich", left="transactions", right="users", on="user_id", how="left")
pipeline = join | scorer
result = pipeline({"transactions": txns_df, "users": users_df})
Save and load
import asyncio
from decider.config.file import JsonFileConfigManager
from decider.modules import GraphModule
mgr = JsonFileConfigManager(basepath="./configs")
asyncio.run(scorer.asave("main", mgr))
asyncio.run(mgr.save_version(overwrite=True))
# Reconstruct from disk
fresh = JsonFileConfigManager(basepath="./configs")
loaded = asyncio.run(fresh.get_latest())
module = GraphModule.model_validate(loaded.config["main"]).root
CLI
# Scaffold a new module
decider template module CreditScorer
# Scaffold a module into a shareable package
decider template module CreditScorer --package mylib
# Scaffold a new project
decider template project fraud_detection
# Start a server
decider serve # starlette on :8080
decider serve --engine sanic --workers 4
decider serve --port 9000 --reload
# Launch the interactive graph visualiser
decider visualise --project-dir projects/loan_scoring
Jupyter magic
%load_ext decider.magics
%%module CreditScorer
class CreditScorerConfig(BaseModel):
weight: float = 200.0
def dti_ratio(debt: pl.Expr, income: pl.Expr) -> pl.Expr:
return debt / income
def credit_score(dti_ratio: pl.Expr, config: CreditScorerConfig) -> pl.Expr:
return pl.lit(800) - dti_ratio * config.weight
This writes decider_extensions/credit_scorer/__init__.py, reloads it, and injects CreditScorer into the notebook namespace. Add --package mylib to write into a proper uv src-layout package instead.
Contributing
We welcome contributions to Decider! Please refer to our Contributing Guide for full details.
- Fork the repository and create a branch from
main. - Install dependencies with
uv sync --all-extras. - Run tests with
uv run pytest. - Submit a Pull Request with a clear description of your changes.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Thank you for your interest in Decider! We look forward to your contributions and feedback.
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 decider-0.0.1.tar.gz.
File metadata
- Download URL: decider-0.0.1.tar.gz
- Upload date:
- Size: 346.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e044e3c850e9d70ada964e2d0960d5664f684547c19ca3be4bd4183b5f43043c
|
|
| MD5 |
9523832069d40cec83aa18abba27f25d
|
|
| BLAKE2b-256 |
f814a7f76866660b9425b96068d7bf8a0199dfa73cb66fbaf239ddcee5f33afe
|
Provenance
The following attestation bundles were made for decider-0.0.1.tar.gz:
Publisher:
release.yml on capitec/dsp-decision-engine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
decider-0.0.1.tar.gz -
Subject digest:
e044e3c850e9d70ada964e2d0960d5664f684547c19ca3be4bd4183b5f43043c - Sigstore transparency entry: 1925257237
- Sigstore integration time:
-
Permalink:
capitec/dsp-decision-engine@8400c402af99ee85ff84753a80349e30ed7425a6 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/capitec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8400c402af99ee85ff84753a80349e30ed7425a6 -
Trigger Event:
push
-
Statement type:
File details
Details for the file decider-0.0.1-py3-none-any.whl.
File metadata
- Download URL: decider-0.0.1-py3-none-any.whl
- Upload date:
- Size: 130.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50d2604f277ceef6fb3a9420fd439c31cd8f9f4af48a23d5e675e9b4b17e4696
|
|
| MD5 |
60e97541b15cc7f920936bb2d44ea453
|
|
| BLAKE2b-256 |
5d0f146398f7d702f6a25063c82f8682bb980fd6ded006267ab84a48bbd8fea6
|
Provenance
The following attestation bundles were made for decider-0.0.1-py3-none-any.whl:
Publisher:
release.yml on capitec/dsp-decision-engine
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
decider-0.0.1-py3-none-any.whl -
Subject digest:
50d2604f277ceef6fb3a9420fd439c31cd8f9f4af48a23d5e675e9b4b17e4696 - Sigstore transparency entry: 1925257562
- Sigstore integration time:
-
Permalink:
capitec/dsp-decision-engine@8400c402af99ee85ff84753a80349e30ed7425a6 -
Branch / Tag:
refs/tags/v0.0.1 - Owner: https://github.com/capitec
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@8400c402af99ee85ff84753a80349e30ed7425a6 -
Trigger Event:
push
-
Statement type: