Skip to main content

Unobstrusive functional programming in Python.

Project description

Purely 💧

A lightweight elixir for cleaner, safer, and more fluent Python.

PyPI - Version PyPi - Python Version Github - Open Issues PyPi - Downloads (Monthly) Github - Commits


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: Option for null-safety and Result for explicit error handling.
  • Safe Navigation: Access deeply nested attributes without worrying about AttributeError.
  • Fluent Pipelines: Chain operations and vectorize list transformations with Chain and pipe.
  • 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.

  1. Setup: Clone the repository and run make.
  2. Formatting: Ensure code adheres to black formatting using make format.
  3. Testing: Run the full test suite with make test-all.

📝 License

Distributed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

purely-1.0.tar.gz (28.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

purely-1.0-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

File details

Details for the file purely-1.0.tar.gz.

File metadata

  • Download URL: purely-1.0.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

Hashes for purely-1.0.tar.gz
Algorithm Hash digest
SHA256 3b30e7bb9e6007ec0ae90324dfd4b72741ab682d1085bbc5ee38d4b1d4dba321
MD5 0dcadd9eac04eff08ca6c09d6a086a2e
BLAKE2b-256 61b454029d642e67e46c0cdfdfc51e186cfd3e71b795264ff8d091da3ef2494c

See more details on using hashes here.

File details

Details for the file purely-1.0-py3-none-any.whl.

File metadata

  • Download URL: purely-1.0-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

Hashes for purely-1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 89e9887b41fcb54de7d361a19a61e7da5001d426a035b6aa9435e8f40254657e
MD5 e3a7896a9d6c71dcf00238ab657ea2f6
BLAKE2b-256 de5b21a41b70f6b8e6422ce50ec116619f7e138a870182319ffe7fa55bdc3a76

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page