Skip to main content

Type-safe railway-oriented programming (ROP)

Project description

trcks 🚂🐍

trcks is a Python library that allows railway-oriented programming (ROP) in two different type-safe programming styles.

Setup

trcks is available on PyPI. Use your favorite package manager (e.g. pip, poetry or uv) to install it. Check the documentation for detailed setup instructions.

Usage

The following subsections demonstrate both styles of railway-oriented programming (ROP) supported by trcks.

Object-oriented style

The object-oriented style is based on method chaining, as demonstrated by the get_subscription_fee_by_email function in the following example.

>>> from typing import Literal
>>> from trcks import Result
>>> from trcks.oop import Wrapper
>>>
>>> UserDoesNotExist = Literal["User does not exist"]
>>> UserDoesNotHaveASubscription = Literal["User does not have a subscription"]
>>> FailureDescription = UserDoesNotExist | UserDoesNotHaveASubscription
>>>
>>> def get_user_id(user_email: str) -> Result[UserDoesNotExist, int]:
...     if user_email == "erika.mustermann@domain.org":
...         return "success", 1
...     if user_email == "john_doe@provider.com":
...         return "success", 2
...     return "failure", "User does not exist"
...
>>> def get_subscription_id(
...     user_id: int
... ) -> Result[UserDoesNotHaveASubscription, int]:
...     if user_id == 1:
...         return "success", 42
...     return "failure", "User does not have a subscription"
...
>>> def get_subscription_fee(subscription_id: int) -> float:
...     return subscription_id * 0.1
...
>>> def get_subscription_fee_by_email(
...     user_email: str
... ) -> Result[FailureDescription, float]:
...     return (
...         Wrapper(core=user_email)
...         .map_to_result(get_user_id)
...         .map_success_to_result(get_subscription_id)
...         .map_success(get_subscription_fee)
...         .core
...     )
...
>>> get_subscription_fee_by_email("erika.mustermann@domain.org")
('success', 4.2)
>>> get_subscription_fee_by_email("john_doe@provider.com")
('failure', 'User does not have a subscription')
>>> get_subscription_fee_by_email("jane_doe@provider.com")
('failure', 'User does not exist')

Notes w.r.t. object-oriented style

  1. The generic type trcks.Result allows domain errors to become part of a function's return type (subject to static type checking).
  2. The class trcks.oop.Wrapper provides a convenient way to chain trcks.Result-returning functions and "regular" functions (in a type-safe way).

Functional style

The functional style is based on function composition, as demonstrated by the get_subscription_fee_by_email function in the following example.

>>> from typing import Literal
>>> from trcks import Result
>>> from trcks.fp.composition import Pipeline3, pipe
>>> from trcks.fp.monads import result as r
>>>
>>> UserDoesNotExist = Literal["User does not exist"]
>>> UserDoesNotHaveASubscription = Literal["User does not have a subscription"]
>>> FailureDescription = UserDoesNotExist | UserDoesNotHaveASubscription
>>>
>>> def get_user_id(user_email: str) -> Result[UserDoesNotExist, int]:
...     if user_email == "erika.mustermann@domain.org":
...         return "success", 1
...     if user_email == "john_doe@provider.com":
...         return "success", 2
...     return "failure", "User does not exist"
...
>>> def get_subscription_id(
...     user_id: int
... ) -> Result[UserDoesNotHaveASubscription, int]:
...     if user_id == 1:
...         return "success", 42
...     return "failure", "User does not have a subscription"
...
>>> def get_subscription_fee(subscription_id: int) -> float:
...     return subscription_id * 0.1
...
>>> def get_subscription_fee_by_email(
...     user_email: str
... ) -> Result[FailureDescription, float]:
...     # Explicitly assigning a type to `pipeline` might
...     # help your static type checker understand that
...     # `pipeline` is a valid argument for `pipe`:
...     pipeline: Pipeline3[
...         str,
...         Result[UserDoesNotExist, int],
...         Result[FailureDescription, int],
...         Result[FailureDescription, float],
...     ] = (
...         user_email,
...         get_user_id,
...         r.map_success_to_result(get_subscription_id),
...         r.map_success(get_subscription_fee),
...     )
...     return pipe(pipeline)
...
>>> get_subscription_fee_by_email("erika.mustermann@domain.org")
('success', 4.2)
>>> get_subscription_fee_by_email("john_doe@provider.com")
('failure', 'User does not have a subscription')
>>> get_subscription_fee_by_email("jane_doe@provider.com")
('failure', 'User does not exist')

Notes w.r.t. functional style

  1. The generic type trcks.Result allows domain errors to become part of a function's return type (subject to static type checking).
  2. The modules trcks.fp.composition and trcks.fp.monads.result provide a convenient way to chain trcks.Result-returning functions and "regular" functions (in a type-safe way).

Still not convinced?

Explore these repositories for more ways to use trcks:

  1. trcks-example-cyclopts (CLI application)
  2. trcks-example-fastapi (REST backend application)

Or check the motivation for railway-oriented programming and for trcks.

Project details


Download files

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

Source Distribution

trcks-0.4.4.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

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

trcks-0.4.4-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file trcks-0.4.4.tar.gz.

File metadata

  • Download URL: trcks-0.4.4.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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 trcks-0.4.4.tar.gz
Algorithm Hash digest
SHA256 54e1082a35960ee351e8c23e27bbc3774f42dba57efe00781aaddb67418c324f
MD5 bdacffe418090e209aead1e323b98fac
BLAKE2b-256 3dc753daafb90f4d642f16b98f7f401957707addcd136a0421268c475b9a44c6

See more details on using hashes here.

File details

Details for the file trcks-0.4.4-py3-none-any.whl.

File metadata

  • Download URL: trcks-0.4.4-py3-none-any.whl
  • Upload date:
  • Size: 25.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","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 trcks-0.4.4-py3-none-any.whl
Algorithm Hash digest
SHA256 0b50eede5e0662ecc9586887a001b540e11bb0b40738ff9bd3d1e8645af56f5b
MD5 a81576eb776943ef64a1764011a4d79a
BLAKE2b-256 cb25b9bd8379efb051a8a8c1ef41428b40f65152d3e53bc9ae6bbc1a52bbede9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page