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.1.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.1-py3-none-any.whl (10.9 kB view details)

Uploaded Python 3

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

Hashes for purely-1.1.tar.gz
Algorithm Hash digest
SHA256 5f9d2f4feda9c357630c8ca4a3606185979ace23cf92aeff11e2f96743c6a41c
MD5 1cd4a32fe9c0d4835075c9610afda921
BLAKE2b-256 333fa146ace0ccc244a233dd5a23629b0134d8f364c99cad28d9c2c66b29a5dc

See more details on using hashes here.

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

Hashes for purely-1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 bff64e4f6af6584dec15de803d709640c0b43e6b6716c8f725575a179a0ea6e5
MD5 172545c5b0213ba7890462f83de6887f
BLAKE2b-256 babd35e5e55a8abfd150fd8ca63fb5311d1ee1b4919dc741cbcd9cf175f7582b

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