Unobstrusive functional programming in Python.
Project description
Purely 💧
A lightweight elixir for cleaner, safer, and more fluent Python.
Purely is a zero-dependency library designed to bring the best parts of functional programming—safety, pipelines, and immutability—into Python without the academic overhead or complex types. It allows you to write code that reads from top to bottom, rather than inside out.
📦 Installation
Purely leverages modern Python features (generics) and requires Python 3.12+.
pip install purely
✨ Features
- Functional Containers:
Optionfor null-safety andResultfor explicit error handling. - Safe Navigation: Access deeply nested attributes without worrying about
AttributeError. - Fluent Pipelines: Chain operations and vectorize list transformations with
Chainandpipe. - Architectural Decoupling: Interface-aware Dependency Injection and Multiple Dispatch.
- Ergonomic Currying: Partial application with full type-hint support for IDEs.
📚 User Guide
1. Functional Containers (Low-Level)
Option
Handles missing values without explicit if x is None checks.
from purely import Option
# Option(10) -> convert to 20 -> keep because > 5
val = Option(10).convert(lambda x: x * 2).keepif(lambda x: x > 5)
assert val.unwrap() == 20
# Option(None) -> chain broken
empty = Option(None).convert(lambda x: x + 1)
assert empty.is_none()
Result
Explicit success (Ok) or failure (Err) states for "Railway Oriented Programming".
from purely.result import Ok, Err
def divide(a, b):
return Ok(a / b) if b != 0 else Err("Division by zero")
# Fluent error handling without try/except blocks
res = divide(10, 2).then(lambda x: x * 2).unwrap()
assert res == 10.0
ensure
Asserts the existence of a value. It unwraps an Option or checks if a raw value is None, raising a custom error if the check fails.
from purely import ensure, Option
# Unwraps a Some(value)
val = ensure(Option(10))
assert val == 10
# Raises ValueError if None
try:
ensure(None, error="Missing data")
except ValueError as e:
print(e) # "Missing data"
2. Flow & Navigation (Middle-Level)
safe
Enables null-safe navigation through nested objects while maintaining IDE autocompletion.
from purely import safe, ensure
user = get_user_or_none() # Could be None
# Access .address.city.name safely. Returns Option(None) if any step fails.
city_name = safe(user).address.city.name
# Crash intentionally only when you must have the value
print(ensure(city_name, "City name is required"))
pipe
A simple utility to pass a value through a sequence of functions.
from purely import pipe
# 5 -> 10 -> 20 -> "20"
result = pipe(5, lambda x: x * 2, lambda x: x + 10, str)
assert result == "20"
Chain
A unified container for pipelines, vectorized list operations, and captured error handling.
from purely import Chain
names = (
Chain(["alice", "bob", "charlie"])
.filter(lambda n: len(n) > 3) # ["alice", "charlie"]
.map(lambda n: n.upper()) # ["ALICE", "CHARLIE"]
.unwrap()
)
# Exception handling inside the chain
recovered = (
Chain(10)
.then(lambda x: x / 0) # Captures ZeroDivisionError
.catch(lambda e: 0) # Recovers with fallback
.unwrap()
)
assert recovered == 0
3. High-Level Architecture
curry
Transforms functions of multiple arguments into a series of partial applications.
from purely import curry
@curry
def multiply(a, b):
return a * b
double = multiply(2)
assert double(10) == 20
Registry (Dependency Injection)
A scoped, interface-aware DI system with automatic MRO (Method Resolution Order) discovery.
from purely import Registry, depends
reg = Registry()
# Registering Postgres automatically satisfies Database and Storage interfaces
reg.register(PostgresDatabase())
@reg.inject
def save_user(user, db: Database = depends(Database)):
db.save(user)
save_user(new_user)
dispatcher (Multiple Dispatch)
Runtime polymorphism based on type signatures and value-based predicates.
from purely import dispatcher
@dispatcher
def process(data):
return "Generic data"
@process.dispatch
def _(data: list):
return f"List of length {len(data)}"
@process.when(lambda d: isinstance(d, int) and d < 0)
def _(data):
return "Negative number"
assert process([1, 2]) == "List of length 2"
assert process(-5) == "Negative number"
🛠 Contribution
We use uv for dependency management and a makefile for orchestration.
- Setup: Clone the repository and run
make. - Formatting: Ensure code adheres to
blackformatting usingmake format. - Testing: Run the full test suite with
make test-all.
📝 License
Distributed under the MIT 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 purely-1.1.tar.gz.
File metadata
- Download URL: purely-1.1.tar.gz
- Upload date:
- Size: 28.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f9d2f4feda9c357630c8ca4a3606185979ace23cf92aeff11e2f96743c6a41c
|
|
| MD5 |
1cd4a32fe9c0d4835075c9610afda921
|
|
| BLAKE2b-256 |
333fa146ace0ccc244a233dd5a23629b0134d8f364c99cad28d9c2c66b29a5dc
|
File details
Details for the file purely-1.1-py3-none-any.whl.
File metadata
- Download URL: purely-1.1-py3-none-any.whl
- Upload date:
- Size: 10.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bff64e4f6af6584dec15de803d709640c0b43e6b6716c8f725575a179a0ea6e5
|
|
| MD5 |
172545c5b0213ba7890462f83de6887f
|
|
| BLAKE2b-256 |
babd35e5e55a8abfd150fd8ca63fb5311d1ee1b4919dc741cbcd9cf175f7582b
|