Skip to content
Logo

Introduction

effecton is a typed effect system for Python, inspired by Effect-TS. It is early stage and experimental.

The core type is Effect[A, E, R]: a description of a computation that succeeds with A, fails with a typed error E, and requires R dependencies. Building an effect performs no work. You get a plain value that you can compose, pass around and test, and a runner executes it at the edge of your program.

Installation

effecton requires Python 3.14 or later.

uv add effecton
# or
pip install effecton

A first program

Consumer code imports the package once as E and reaches everything through it. Hover any name in the snippet to see the type ty infers for it.

from dataclasses import 
from typing import 
 
import effecton as E
 
 
# Custom errors extend EffectonError and are final: one leaf class per cause
@
@(frozen=True)
class (E.):
    : 
 
 
# Succeeds with str, fails with SecretInvalidError or an HTTP error,
# and requires an HttpClient
@E.
def () -> E.[
    ,
     | E.HttpClient. | E.HttpClient.,
    E.HttpClient.,
]:
     = yield from E.(E.HttpClient.)
 
     = yield from .("https://example.com/secret")
     = yield from E.HttpClient.()
    if . != "hunter2":
        yield from E.((.))
    return .
 
 
# The program can only run once its requirements are provided
programArrow
= ().(E.HttpClient.)(E.HttpClient.())
match E.(): case E.(value): () # "hunter2" case E.(cause): () # Fail(SecretInvalidError(...)) or Fail(StatusError(...))

Three things happen here:

  • Errors are typed. The signature lists every way check_secret can fail. catch handles one error class at a time and removes it from the type, so the checker knows what is left.
  • Requirements are visible. E.require puts HttpClient.Protocol into the R channel. Forgetting to provide it is a type error, not a runtime surprise, and a test can provide E.HttpClient.Test instead of the live client.
  • Nothing runs until you say so. program is a value. run_sync_exit interprets it and returns an Exit to match on. run_sync returns the value and raises on failure.

What you get

  • Type-safe errors. One frozen EffectonError dataclass per failure cause, precise unions in signatures, and catch / catch_all to handle them.
  • Dependency injection. Requirements are part of the type. Provide them one at a time with provide(T)(impl).
  • Resource management. on_exit finalizers and Scope release resources in reverse order, on success and failure alike.
  • Generator syntax. @E.gen with yield from reads like ordinary sequential code while staying fully typed.
  • A standard library. Logger, Clock, Random, FileSystem, Process and HttpClient services, each with a live and a test implementation, plus fibers, racing, timeouts and retries.
  • Sync and async runners. The same effect runs under run_sync or run_async. run_main is the entry point for CLIs and reports failures with proper exit codes.

Where next

The docs are being written. Until they cover more ground, the README on GitHub walks through every feature with examples, and skills-cli is a small CLI built entirely on effecton services.