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.2.tar.gz (29.0 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.2-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: purely-1.2.tar.gz
  • Upload date:
  • Size: 29.0 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.2.tar.gz
Algorithm Hash digest
SHA256 373766796fab5b28d3db1c9eb92103a77887d8f27aed4d82c5558217f6c54787
MD5 cdac703c6480a6b66f78c80f30fe241d
BLAKE2b-256 628c37d8496b13b2aed4f49efb347855ea52e7bf1281fef9ec6a7a58f6bc812b

See more details on using hashes here.

File details

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

File metadata

  • Download URL: purely-1.2-py3-none-any.whl
  • Upload date:
  • Size: 11.0 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 0aecfbec6ae71aeac4d7b45df56151473a730120629bb77f038302ddeb30a515
MD5 50173fb77ae93144c9ca0e73ef4b6001
BLAKE2b-256 61f91cfb90caecbfe1bb71139397085b310bf7d1ad44fb8e2cc11b762287c3fb

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