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.

Modeling Types

newtype makes a UserId distinct from a bare int for type checkers, while it behaves like one at runtime.

from pyrula import newtype, newtype_strict
UserId = newtype("UserId", int)
UserId(1) # 1, typed as UserId
Email = newtype_strict("Email", str, validate=lambda s: "@" in s)
Email("[email protected]") # ok
Email("nope") # raises ValueError

newtype_strict also takes err_msg for the failure message.

@sealed marks a closed set of cases and @case declares each as an immutable dataclass. match runs the first matching on(Type, handler) and raises if none match.

from pyrula import sealed, case, match, on
@sealed
class Shape:
pass
@case
class Circle(Shape):
radius: float
@case
class Rect(Shape):
w: float
h: float
def area(shape: Shape) -> float:
return match(shape)(
on(Circle, lambda c: 3.14159 * c.radius ** 2),
on(Rect, lambda r: r.w * r.h),
)
area(Circle(2.0)) # 12.566...

A sealed type accepts subclasses only from its defining module; any other raises TypeError at class creation, with no opt-out for tests.

Cases are frozen dataclasses, so dataclasses.replace and asdict work:

import dataclasses
c = Circle(2.0)
dataclasses.replace(c, radius=3.0) # Circle(radius=3.0)
dataclasses.asdict(c) # {"radius": 2.0}

The JSON form is {"CaseName": {fields}}; see JSON form.

View this page as Markdown