Railway-oriented computation builders for typed Python workflows
Project description
Pythonic Computation Expression Builders
comp-builders
comp-builders brings railway-oriented programming patterns to typed Python through small, dependency-free computation builders. It lets you write readable workflows that sequence Result, Option, AsyncResult, and Validation values without deeply nested if statements or exception-heavy control flow.
Motivation and Purpose
Many production Python workflows have the same shape: parse input, validate it, call a service, transform a response, and stop early when something goes wrong. Traditional Python handles that with exceptions, sentinel values, or repeated conditional checks. Those techniques work, but they can hide failure paths and make the happy path harder to read.
Railway-oriented programming makes success and failure explicit. A workflow moves along a success track until a step returns a failure, absence, or validation error. comp-builders keeps that idea lightweight and Pythonic by using generator-based blocks rather than introducing a large functional programming framework.
Key features
Result,Ok, andErrfor computations that can succeed or fail.Option,Some, andNothingfor optional values withoutNonechecks scattered through a workflow.AsyncResultfor awaitable operations that return explicit success or failure values.Validation,Valid, andInvalidfor accumulating independent validation errors.- Decorator-based builders:
@result.block,@option.block,@async_result.block, and@validation.block. - Typed public API with
py.typed, runtime tests, and mypy coverage. - No runtime dependencies.
Railway-oriented programming in one minute
In functional languages, railway-oriented programming is often built from Result/Either types and bind operators:
let createUser raw =
raw
|> parseJson
|> Result.bind readUserId
|> Result.bind saveUser
A traditional Python version often repeats branching logic after every step:
def create_user(raw: str):
parsed = parse_json(raw)
if not parsed.ok:
return parsed
user_id = read_user_id(parsed.value)
if not user_id.ok:
return user_id
return save_user(user_id.value)
With comp-builders, each yield unwraps an Ok value or short-circuits on Err:
from comp_builders import Err, Ok, Result, result
def parse_user_id(raw: str) -> Result[str, str]:
value = raw.strip()
return Ok(value) if value else Err("missing user id")
@result.block
def create_user_id(raw: str):
user_id = yield parse_user_id(raw)
return user_id.upper()
assert create_user_id(" ada ") == Ok("ADA")
assert create_user_id(" ") == Err("missing user id")
For a longer explanation with functional-language comparisons, traditional Python examples, and package-based Python examples, see Railway-Oriented Programming for Python Users.
How it works
The core abstraction is Builder.block. Concrete builders define how yielded values are unwrapped and how final values are wrapped. For example, ResultBuilder unwraps Ok(value), stops on Err(error), and wraps a completed return value as Ok(value).
Install
uv add comp-builders
For local development from this repository:
uv sync --all-groups
uv run pytest
uv run mypy .
uv run ruff check .
Minimal examples
Option
from comp_builders import Nothing, Some, option
@option.block
def first_initial(user: dict[str, object]):
name = yield user.get("name", Nothing)
return name[0]
assert first_initial({"name": Some("Ada")}) == Some("A")
assert first_initial({}) is Nothing
Validation
from comp_builders import Invalid, Valid, validation
@validation.block
def validate_user(data: dict[str, str]):
name = yield Valid(data["name"]) if data.get("name") else Invalid("missing name")
yield Valid(data["email"]) if "@" in data.get("email", "") else Invalid("invalid email")
return name
assert validate_user({"name": "", "email": "bad"}) == Invalid(
("missing name", "invalid email")
)
Documentation
- Quickstart - five-minute setup and first workflow.
- User guide - core workflows and usage patterns.
- API reference - public classes, functions, and builders.
- Railway-oriented programming guide - conceptual guide with comparisons.
- Examples - runnable API, ETL, and ML workflow examples.
- Architecture - implementation notes for maintainers.
- Changelog - user-facing release history.
Compatibility
comp-builders supports Python 3.11 and newer. It is distributed as a typed package and includes a py.typed marker.
License
Apache License 2.0. See LICENSE.
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 comp_builders-1.0.0.tar.gz.
File metadata
- Download URL: comp_builders-1.0.0.tar.gz
- Upload date:
- Size: 1.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdeb53e582c1ec36496ef404da4e285cbe1e14be9311c7ceed695fa678d56ce7
|
|
| MD5 |
5620b13f07466173e54bb2ea9ebf5755
|
|
| BLAKE2b-256 |
f59d01553d85bacd086146574ddfde6acd6a99669f17c9f94bc2afa6ae064be3
|
File details
Details for the file comp_builders-1.0.0-py3-none-any.whl.
File metadata
- Download URL: comp_builders-1.0.0-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
95b0854d6893ada24a258d6b23a973550d439898784a5307703fe67cf560e6e0
|
|
| MD5 |
f808b766594f3b6d1b98cc900f415394
|
|
| BLAKE2b-256 |
71456f539b5661311b0d374780c71888433466a77068e8dae3357217c7daa920
|