{"id":17341081,"url":"https://github.com/davidvujic/pythonic-railway","last_synced_at":"2025-04-14T19:09:05.371Z","repository":{"id":59829854,"uuid":"412828386","full_name":"DavidVujic/pythonic-railway","owner":"DavidVujic","description":"Experimenting with Railway oriented programming and Python","archived":false,"fork":false,"pushed_at":"2022-03-14T17:07:53.000Z","size":27,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-14T19:08:59.661Z","etag":null,"topics":["experiments","functional-programming","python","railway-oriented-programming"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DavidVujic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-10-02T15:01:35.000Z","updated_at":"2025-03-14T06:45:23.000Z","dependencies_parsed_at":"2022-09-22T18:12:51.573Z","dependency_job_id":null,"html_url":"https://github.com/DavidVujic/pythonic-railway","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidVujic%2Fpythonic-railway","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidVujic%2Fpythonic-railway/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidVujic%2Fpythonic-railway/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidVujic%2Fpythonic-railway/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavidVujic","download_url":"https://codeload.github.com/DavidVujic/pythonic-railway/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248943456,"owners_count":21186958,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["experiments","functional-programming","python","railway-oriented-programming"],"created_at":"2024-10-15T15:47:42.872Z","updated_at":"2025-04-14T19:09:05.352Z","avatar_url":"https://github.com/DavidVujic.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A Pythonic Railway\n\nThis is an attempt to implement a lightweight, or perhaps a sloppy,\nversion of Railway Oriented Programming in Python.\n\nWhat I'm trying to achieve with the example code in this repo,\nis to use the functional concepts of Railway Oriented Programming.\nAt the same time I want to keep a Pythonic mindset, and not go all-in functional.\n\n## Why Railways?\nAs I understand Railway Oriented Programming, it is about adding error handling and still keeping a \"happy path\" style in the code.\nThis is done by wrapping or decorating functions.\n\nThe functions are wrapped (or decorated, as in the examples in this repo) to catch failures. The output of a failed function call will\nbe the input to the next one. The next function will choose track based on the input: the success track or the fail track.\n\nBy using a two-tracked approach in functions, the error handling will be separated from the program.\n\nFunctions will be less cluttered with `try except` error handling and also `if else` flow control clauses. In many cases,\nthis will mean that the amount of code within functions will be a lot less. Less is more.\n\n\n## What's in this repo?\nTurn a single track function, into a two-track Railway by using decorators.\n\nbefore:\n\n``` python\ndef get_headers(data):\n    return data[0].keys()\n```\n\nafter:\n\n``` python\n@railway.tracks\ndef get_headers(data):\n    return data[0].keys()\n```\n\nThe `tracks` decorator will turn the `get_headers` function into a two-tracked railway,\nby wrapping the function call. If the code in the function causes an 💥 Exception 💥 (such as an IndexError or TypeError),\nan object of `Fail` type will be returned. The following functions will be bypassed when a `Fail`\nobject is passed in as an argument.\n\nIf all goes well, the data will be returned, just as it would without the decorator.\n\n_Side note: In a proper Railway Oriented Programming implementation,\na `Success` object should be returned. But I think that\njust returning the data has a nice keep-it-simple approach._\n\nThere's also a `true-false` wrapper for boolean functions. This one can be used when\na `False` result should exit the sequence. The wrapper creates a two-track function that will\nreturn a `Fail` object if the result is `False`. The following functions in the sequence will be\nbypassed.\n\n``` python\n@railway.tracks_boolean\ndef has_valid_headers(headers):\n    true_or_false = map(lambda header: True if header else False, headers)\n\n    return False not in set(true_or_false)\n```\n\n## Some syntactic sugar\nIn the `funcs` module contains functionality to implement a functions pipeline.\nThis one is inspired by threading macros in Clojure.\n\nThe first argument to `pipe` is the input value to the first function in the sequence.\nThe output from the first function is the input to the next one.\n``` python\nres = pipe(\"path/to/file.csv\", parse, get_headers, has_valid_headers)\n```\n\nOr, without the pipe function:\n\n``` python\ndata = parse(\"path/to/file.csv\")\nheaders = get_headers(data)\nis_valid = has_valid_headers(headers)\n```\n\nIf something has gone wrong somewhere in the sequence, `res` will be a `Fail` object.\nOtherwise it will be the output from the last function in the sequence.\n\n## References\nDon't miss the NDC London talk\n[Railway oriented programming: Error handling in functional languages by Scott Wlaschin](https://vimeo.com/113707214)\n\nDo you want to go all-in functional Python? Have a look at [returns](https://returns.readthedocs.io/en/latest/index.html)\nand [toolz](https://github.com/pytoolz/toolz)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidvujic%2Fpythonic-railway","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidvujic%2Fpythonic-railway","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidvujic%2Fpythonic-railway/lists"}