Skip to main content

pyeffect

Handle expected failures as values — a fully typed Result, Option, and lazy Effect core for Python 3.14+.

from pyeffect import Err, Ok, Result, attempt

result: Result[int, str] = attempt(lambda: int("42")).map(lambda n: n * 2)
match result:
    case Ok(value):
        print(value)  # 84
    case Err(error):
        print(f"failed: {error}")

What it is

pyeffect is a functional core for Python that turns expected failure (network errors, bad input, absence) into values you can compose, instead of exceptions you must catch. Result and Option handle the data; Effect defers side effects until you run them.

The one rule

Bugs panic, expected failures return values.

  • Panic when the program reaches a state that should be impossible — unwrap() on Err/Nothing, a broken retry Policy, an over-arity curry. The damage stops at the exact line.
  • Return a value when failure is expected — network errors, bad input, absence. The caller decides what to do with it.

Install

Not on PyPI yet — clone the repo and install from source:

uv sync

That installs pyeffect and the dev dependencies (pytest, ruff, ty) into .venv.

A complete example

Paste this and it runs — expected output is in the comments:

from pyeffect import Effect, Err, Ok, Result, attempt, from_optional

# Expected failure is a value the caller handles.
result: Result[int, str] = attempt(lambda: int("42")).map(lambda n: n * 2)
match result:
    case Ok(value):
        print(value)  # 84
    case Err(error):
        print(f"failed: {error}")

# Absence is a value too.
assert from_optional({"a": 1}.get("b")).unwrap_or(0) == 0

# Side effects are deferred until you run them.
effect = (
    Effect.attempt(lambda: open("config.json").read())
    .map(str.strip)
    .context("while loading config")
    .catch(lambda e: Effect.success("{}"))
)
print(effect.run())  # {} — the read failed, the fallback ran

What's inside

Module Types
pyeffect.result Ok / Err / Result[T, E], attempt, guard, traverse, flatten, transpose, ErrorContext
pyeffect.option Some / Nothing / Option[T], from_optional, flatten, transpose
pyeffect.effect Effect[T, E] — a lazy, re-runnable computation; sequence, attempt, retry_result
pyeffect.retry retry + Policy — deterministic, injectable backoff
pyeffect.pipe pipe(value, f, g, ...) — left-to-right threading
pyeffect.compose compose, curry, lift / lift2 / lift3, identity, tap, flip, unpack, constant, partial

The combinators are methods: Effect carries map, and_then, catch, context, inspect, inspect_err, map_or, flatten, zip, and retry; every variant has the eager boolean combinators and_ / or_ (trailing underscore because and/or are Python keywords), plus Option.xor.

flatten and transpose are module-level functions on result and option (from pyeffect.result import flatten, transpose) because Python's invariant generics make the equivalent methods untypeable at call sites.

Effect is the impurity boundary

Python is eager: every call executes immediately, so purity is lost the moment you touch IO or time. Effect restores it — constructing and composing run nothing; run() / run_result() are the only impure moments. Dependencies (readers, clocks, backends) are captured in the thunk's closure, and sleep is injected, so tests run with zero delay and no real I/O.

The library is sync-first by design: the value layer (Result, Option, pipe, compose) is pure and async-agnostic, and Effect is a sync thunk over Result. An AsyncEffect sibling (awaitable thunks, asyncio.sleep-injected retry) is planned for when network use-cases demand it — not before.

Typing notes

Every combinator is checked with ty as part of the test suite (tests/typing/ pins the contracts, including intentional failures that must stay rejected). Known constraints of Python generics, documented in the module docstrings:

  • No variance in PEP 695Effect.success / Effect.failure leave the unbound slot as Any; anchor the chain with an annotation or an operation that fixes the slot (attempt's catch, map_err, context).
  • No higher-kinded types — there are no generic Functor / Monad typeclasses; each type implements map / and_then structurally.
  • Fixed arity ceilingspipe / compose type-check up to ten functions, curry up to five; the runtimes accept more.

Development

uv run pytest      # runtime + doctest tests
uv run ty check src tests   # typing contract; also gates the tests/typing/ fixtures
uv run ruff check src tests

Download files

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

Source Distribution

pyeffect-0.1.0.tar.gz (14.2 kB view details)

Uploaded Source

Built Distribution

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

pyeffect-0.1.0-py3-none-any.whl (18.8 kB view details)

Uploaded Python 3

File details

Details for the file pyeffect-0.1.0.tar.gz.

File metadata

  • Download URL: pyeffect-0.1.0.tar.gz
  • Upload date:
  • Size: 14.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","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 pyeffect-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c7ac036d2201994557f066103a4f38590d2a1a49d09bd691b639b9d94b3e18fe
MD5 ad7f5b0fbc38ebfa1278c2b31de095a3
BLAKE2b-256 39461322009ef7cc0100a18628dce8d967e49e4aaa562ab2fb370f90197b6bbd

See more details on using hashes here.

File details

Details for the file pyeffect-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pyeffect-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 18.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","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 pyeffect-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f02a5ef6e83321c22db6ce418d24076fe655681791d933ab24644757c81ebfca
MD5 992894170a797aa355b13e7439de23de
BLAKE2b-256 d0fc0d531b2accb9197eae525da7370df3197a1526d2600b28e954635b155440

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page