result Ok / Err map and and_then error handling without exceptions: working versions (tested 2026-10-16)
Railway-oriented pipelines with the result library: chain fallible steps with map, map_err and and_then, recover with or_else, and turn exceptions into Err values via as_result.
passed6 passed · 0 failed2026-10-16 09:57 UTC- Status
- passed
- Last passed
- 2026-10-16 09:57 UTC
- Tests
- 6 / 6
- Language
- Python
- Runtime
- Python 3.13.16
pinned versions2
result@0.17.0pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
test environment
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38883899161
The code
Exactly the files that passed. Your agent can pull them with get_recipe("result-ok-err-map") (install), or see them on GitHub.
requirements.txt
result==0.17.0 pytest==9.1.1
pipeline.py
from result import Err, Ok, Result, as_result
@as_result(ValueError)
def parse_int(raw: str) -> int:
return int(raw)
def positive(n: int) -> Result[int, str]:
return Ok(n) if n > 0 else Err(f"not positive: {n}")
def reciprocal(n: int) -> Result[float, str]:
return Err("division by zero") if n == 0 else Ok(1 / n)
def run(raw: str) -> Result[float, str]:
# No try/except: each step returns a Result and the chain stops on the first Err.
return (
parse_int(raw)
.map_err(lambda exc: f"bad number {raw!r}: {exc}")
.and_then(positive)
.and_then(reciprocal)
)
tests/test_pipeline.py
import pytest
from result import Err, Ok, Result, UnwrapError
from pipeline import parse_int, run
def test_err_short_circuits_map_and_and_then():
calls = []
err: Result[int, str] = Err("boom")
assert err.map(lambda n: calls.append(n)) == Err("boom")
assert err.and_then(lambda n: Ok(calls.append(n))) == Err("boom")
assert calls == [] # neither op ran, so no exception can leak
def test_map_wraps_but_and_then_flattens():
assert Ok(2).map(lambda n: Ok(n * 2)) == Ok(Ok(4))
assert Ok(2).and_then(lambda n: Ok(n * 2)) == Ok(4)
def test_pipeline_stops_at_first_error_without_exceptions():
assert run("4") == Ok(0.25)
assert run("0") == Err("not positive: 0")
assert run("-2") == Err("not positive: -2")
bad = run("abc")
assert bad.is_err()
assert bad.err_value.startswith("bad number 'abc':")
def test_or_else_recovers_only_on_err():
assert Ok(2).or_else(lambda msg: Ok(-1.0)) == Ok(2)
assert Err("e").or_else(lambda msg: Ok(-1.0)) == Ok(-1.0)
assert run("nope").or_else(lambda msg: Ok(0.0)) == Ok(0.0)
def test_value_access_is_deprecated():
with pytest.deprecated_call():
assert Ok(1).value == 1
with pytest.deprecated_call():
assert Err("boom").value == "boom"
assert Ok(1).ok_value == 1
assert Err("boom").err_value == "boom"
def test_unwrap_keeps_original_exception_as_cause():
err = parse_int("nope")
with pytest.raises(UnwrapError) as info:
err.unwrap()
assert isinstance(info.value.__cause__, ValueError)
with pytest.raises(ValueError):
err.unwrap_or_raise(ValueError)
pytest.ini
[pytest] pythonpath = .
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.