Skip to main content

Decider

Python Version License

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.Expr are 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 columndef dti_ratio(...) produces a dti_ratio column.
  • 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.
  • config parameter → Pydantic model injection — declare config: MyConfig and the config fields are promoted onto the module itself.

Pipelines

Modules compose with two operators:

  • |sequential: step_a | step_b | step_c passes each step's output as the next step's input.
  • &union: module_a & module_b merges 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.

Release files for decider 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for decider 0.0.1
File Size Uploaded
decider-0.0.1.tar.gz 346.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for decider 0.0.1
File Interpreter ABI Platform
decider-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 476.4 kB

Release files / decider-0.0.1.tar.gz

Download URL decider-0.0.1.tar.gz
Size 346.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e044e3c850e9d70ada964e2d0960d5664f684547c19ca3be4bd4183b5f43043c
BLAKE2b-256 checksum
How to use checksums
f814a7f76866660b9425b96068d7bf8a0199dfa73cb66fbaf239ddcee5f33afe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 23, 2026.

Transparency log

Release files / decider-0.0.1-py3-none-any.whl

Download URL decider-0.0.1-py3-none-any.whl
Size 130.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
50d2604f277ceef6fb3a9420fd439c31cd8f9f4af48a23d5e675e9b4b17e4696
BLAKE2b-256 checksum
How to use checksums
5d0f146398f7d702f6a25063c82f8682bb980fd6ded006267ab84a48bbd8fea6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 23, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page