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.

Railway-oriented programming (ROP) styles

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).

Setup

trcks is available on PyPI. Use your favorite package manager (e.g. pip, poetry or uv) to install it.

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.2.tar.gz (24.4 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.2-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: trcks-0.4.2.tar.gz
  • Upload date:
  • Size: 24.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","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.2.tar.gz
Algorithm Hash digest
SHA256 23d3ae1294667734b4342eacf14335079d3d357b2dee4d9958619b2a43a75d4e
MD5 a6a59b77caf11da5a3d28f9a77c18db4
BLAKE2b-256 c5f61a2450a08a42f810d2c83d5a70699820e64fe410830697f470657ae75083

See more details on using hashes here.

File details

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

File metadata

  • Download URL: trcks-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 25.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.24 {"installer":{"name":"uv","version":"0.9.24","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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 5b0c9aea7caaa18d397533eb461ba78984bd12e59a24a0d92c66e43db634deb5
MD5 bbcf6e45ba0e871745dde9703d362341
BLAKE2b-256 07370c94cb63fbea6dd218ced8504574ac48d9888e869afec6a6ca4523c68944

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