Skip to content
Pyrula
Pre-release: the packages are not on PyPI yet, so pip install does not resolve. Build from source to try it today.

Examples

Do-notation reads like straight-line code and short-circuits on failure.

from pyrula import Ok, Err, do
def parse_int(s: str):
try:
return Ok(int(s))
except ValueError:
return Err(f"not an int: {s!r}")
@do
def total(a: str, b: str):
x = yield parse_int(a)
y = yield parse_int(b)
return x + y
total("2", "3") # Ok(5)
total("x", "3") # Err("not an int: 'x'"); second parse never runs

Either.traverse stops at the first error. Validated.traverse and zip check every input.

from pyrula import Either, Err, Invalid, Ok, Valid, Validated
def parse_number(raw: str) -> Either[int, str]:
return Ok(int(raw)) if raw.isascii() and raw.isdigit() else Err(f"not an int: {raw!r}")
def validate_number(raw: str) -> Validated[int, str]:
return Validated.from_either(parse_number(raw))
inputs = ["1", "bad", "3", "also bad"]
assert Either.traverse(inputs, parse_number) == Err("not an int: 'bad'")
assert Validated.traverse(inputs, validate_number) == Invalid([
"not an int: 'bad'", "not an int: 'also bad'",
])
assert Either.traverse(["1", "3"], parse_number) == Ok([1, 3])
from pyrula import Option
cfg = {"timeout": "30"}
timeout = (
Option.of(cfg.get("timeout")) # Some("30") or Nothing
.map(int)
.filter(lambda s: s > 0)
.get_or_else(10) # 30; 10 if missing or non-positive
)

Try.apply captures an Exception; to_either carries it in Err.

from pyrula import Try
result = Try.apply(lambda: int("42")).to_either()
# Ok(42), or Err(ValueError(...)) on bad input
from pyrula import NonEmptyList
scores = NonEmptyList(5, 3, 8)
scores.reduce(lambda a, b: max(a, b)) # 8, no seed value needed
NonEmptyList.from_list([]) # Nothing
from pyrula import IList, Ok, Err
def parse_int(s: str):
try:
return Ok(int(s))
except ValueError:
return Err(f"bad: {s!r}")
results = IList(["1", "two", "3", "four"]).map(parse_int)
ok_vals, errors = results.partition_either()
# ok_vals: IList([1, 3])
# errors: IList(["bad: 'two'", "bad: 'four'"])
from pyrula import Duration
deadline = Duration.seconds(0.5) * 10 # Duration(5s)
deadline.to_millis() # 5000
deadline.to_timedelta() # datetime.timedelta(seconds=5)

View this page as Markdown