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.

🧠 Motivation

Python is a beautiful language, but production code often becomes cluttered with defensive checks and nested function calls.

  1. The "None" Paranoia: We often write 3 lines of code just to check if a variable exists before using it.
  2. Nested Hell: Functional transformations often look like save(validate(parse(read(data)))), which forces you to read backwards.
  3. List Comprehension Fatigue: While powerful, chaining multiple list comprehensions (map/filter/reduce) can quickly become unreadable.

Purely solves this with three core tools: ensure for assertions, safe for null-safe navigation, and Chain for fluent pipelines.

📦 Installation

Purely leverages modern Python features (generics) and requires Python 3.12+.

pip install purely

🚀 Quick Start

The Old Way vs. The Purely Way

from purely import ensure, safe, Chain

# ❌ The Old Way: Defensive and Nested
user_data = get_user(123)
if user_data is None:
    raise ValueError("User not found")

city = None
if user_data.address and user_data.address.city:
    city = user_data.address.city.name.upper()

# ✅ The Purely Way: Fluent and Safe
user_data = ensure(get_user(123), "User not found")

# Null-safe navigation + Pipeline
city = safe(user_data).address.city.name | str.upper

📚 User Guide

1. ensure: The Rusty Unwrap

Stop writing multiline if x is None checks. ensure asserts that a value is not None and returns it, acting as a type-narrowing barrier.

from purely import ensure

# Throws ValueError("API Key missing") if the value is None
api_key = ensure(os.getenv("API_KEY"), "API Key missing")

# Works transparently with Purely's Option/Safe types
name = ensure(safe(user).name)

2. safe: Null-Safe Navigation

Accessing deeply nested attributes on objects that might be None is a common source of AttributeError. The safe utility wraps your object in a proxy that swallows None errors gracefully.

Key Features:

  • Deep Access: Navigate attributes, items, or methods arbitrarily deep.
  • IDE Friendly: Uses type hinting tricks to keep your autocomplete working.
  • Graceful Exit: If any link in the chain is None, the whole chain returns an Option(None).
from purely import safe, ensure

class User:
    def __init__(self, address=None):
        self.address = address

u = User(address=None)

# ❌ Raises AttributeError
# print(u.address.city)

# ✅ Returns Option(None) - No crash
result = safe(u).address.city

# Use ensure() to crash intentionally if the value MUST be there
city = ensure(result, "City is required")

3. Chain: The Fluent Pipe

Chain is a unified wrapper that allows you to pipe values through functions and perform vectorized operations on lists.

Simple Pipelines

Use .then() or the | operator to pass data forward.

from purely import Chain

def double(x): return x * 2

# 5 -> 10 -> "10"
result = Chain(5) | double | str
assert result.unwrap() == "10"

Vectorized Operations

If the wrapped value is a list (or iterable), .map() and .filter() apply to each item in the list, not the list itself.

users = ["alice", "bob", "charlie"]

names = (
    Chain(users)
    .filter(lambda name: len(name) > 3)  # Filters list: ["alice", "charlie"]
    .map(lambda name: name.upper())      # Maps list: ["ALICE", "CHARLIE"]
    .unwrap()
)

Error Handling

Chain captures exceptions effectively, allowing you to handle errors at the end of the pipeline.

result = (
    Chain(10)
    .then(lambda x: x / 0)        # Fails internally (ZeroDivisionError)
    .then(lambda x: x + 10)       # Skipped
    .catch(lambda e: "Recovered") # Catches error and returns fallback
    .unwrap()
)
assert result == "Recovered"

4. Option: Functional Safety

Under the hood, safe uses Option. You can use it directly for functional null handling.

  • .map(func): Transforms value only if it exists.
  • .filter(predicate): Turns value to None if predicate fails.
  • .unwrap(default=...): Extracts value or returns default.
from purely import Option

val = Option(10).map(lambda x: x * 2).filter(lambda x: x > 50)
assert val.is_none()

🛠 Contribution

We use uv for dependency management and makefile for orchestration.

  1. Clone and Setup:

    git clone https://github.com/apiad/purely.git
    cd purely
    make format-check  # Verifies environment
    
  2. Testing: We use pytest with coverage.

    make test-all
    
  3. Formatting: Ensure your code is formatted with black.

    make format
    

📝 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-0.2.1.tar.gz (23.4 kB view details)

Uploaded Source

Built Distribution

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

purely-0.2.1-py3-none-any.whl (6.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: purely-0.2.1.tar.gz
  • Upload date:
  • Size: 23.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","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-0.2.1.tar.gz
Algorithm Hash digest
SHA256 8095ce43eb1c9f531baf0090a9b6ce62d1ea6f7bafb1c366db6d902b8b93021d
MD5 c0d785b6debfcbf3e041193f635de828
BLAKE2b-256 d26f9793b70bde0e2d7c4dd83fe763e0c9cf432f7011701c0d872764e91c718e

See more details on using hashes here.

File details

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

File metadata

  • Download URL: purely-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 6.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.15 {"installer":{"name":"uv","version":"0.9.15","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-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ae1e785adab81381ee21842db85e939c663f3622cd88eea239a6e6e902cd0c8b
MD5 b55b516af0cfbdf0fd37f7aa60680513
BLAKE2b-256 78e10f5568e5299226dfd60da4231f19d05cd6c994380347134dfec3e2ba4016

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