Package: on-rails
on_railsGetting Started »Report a Bug·Request a Feature.Ask a QuestionTable of ContentsAboutPurposeMotivationWhat problems are solved?Getting StartedUsageDocumentationCHANGELOGFeaturesRoadmapSupportFAQProject assistanceContributingAuthors & contributorsSecurityLicenseAbouton_railsis aRailway-Orientedprogramming library for Python. It is designed to help developers write code that
is both easier to read and more resilient to errors. Railway-Oriented programming is a pattern that is similar to
functional programming, but with a focus on handling errors in a more elegant way.Railway OrientedThe Railway Oriented Programming (ROP) pattern separates the pure functional domain logic from the side effects, by
representing them as asequence of functionsthat return an Either type, representing either a successfulResultor an error. This allows for bettercompositionandtestingof functions, as well as improving the code's **
maintainability** andreadability.It also facilitates thehandling of errors, as the error handling logic is separated from the main logic, making it
easier to reason about and handle errors in a consistent and predictable manner. Overall, ROP can lead to more robust
and reliable software systems.Error HandlingIn functional programming, it is not always appropriate to use traditionaltry-exceptblocks because they can lead to
code that is difficult to read, understand, and maintain.on_railssupports functional error handling. The goal of this library is to make error handlingmore explicit,
composable, and testable. By using this library, developers can write code that ismore robust, maintainable, and
expressive.MotivationThe motivation behind this library is the desire to write code that ismore reliable, easier to understand, and less
prone to errors. In many cases, functional programming languages provide built-in abstractions for chaining functions
and handling errors. However, for languages that do not have built-in support, libraries like this can provide a useful
alternative.What problems are solved?Railway Oriented Programming (ROP) solves several common problems in software development, such as:Handling errors:By using an Either (Result) type, ROP makes it easy to represent and handle errors in a
consistent and predictable manner, avoiding errors being thrown and allowing for error handling logic to be separated
from the main logic.Composition:ROP separates the pure functional domain logic from the side effects, such as I/O, by representing
them as a sequence of functions. This makes it easy to compose and chain functions together, enabling better code
reuse and maintainability.Readability:The separation of pure functional domain logic from the side effects makes the code more readable and
understandable, as it makes it clear what each function does and how they relate to each other.Testing:The pure functional domain logic can be easily tested, as it does not depend on any external state or
side effects. This simplifies testing and ensures that the code is correct.Overall, ROP provides a structured approach to software development that makes it easier to handle errors, compose
functions, and test code, leading to more robust and reliable software systems.Developers can spend less time debugging and more time writing code that adds value to their organization. Additionally,
by using functional programming concepts, developers can write code that is easier to reason about and understand, which
can lead to faster development cycles and better quality code.Getting StartedUsepipto install package:pip install on_rails --upgradeUsageSample 1fromon_railsimportResult,def_result@def_result()defget_number()->Result:number=int(input("Enter number: "))returnResult.ok(number)get_number().on_success(lambdavalue:print(f"Number is valid:{value}")).on_fail(lambdaprev:print(prev.detail))Within theget_numberfunction, the user is prompted to enter an integer number. If the user enters a valid integer,
the number is returned as a successful result usingResult.ok(), otherwise, an error message is returned as a failed
result.When the number isnot valid, that is, it cannot be converted to int, anexceptionis raised. Thanks to
thedef_resultdecorator, all exceptions are handled and the error details are saved in the Result detail.Theget_number()function is then called and its result ischainedwith two methods:on_success()andon_fail(). If theget_number()function returns asuccessfulresult, the lambda function passed
toon_success()is executed, which prints the valid number entered by the user. If theget_number()function returns
afailedresult, the lambda function passed toon_fail()is executed, which prints the error message.Sample output forvalidnumber:Enter number: 5
Number is valid: 5Sample output forinvalidnumber:Enter number: a
Title: An exception occurred
Message: invalid literal for int() with base 10: 'a'
Code: 500
Exception: invalid literal for int() with base 10: 'a'
Stack trace: ...Sample 2:fromon_railsimportResult,def_resultfromon_rails.ResultDetails.ErrorsimportValidationError@def_result()defdivide_numbers(a:int,b:int):ifb==0:returnResult.fail(ValidationError(message="Cannot divide by zero"))returnResult.ok(a/b)result=divide_numbers(10,0)ifresult.success:print(f"Operation was successful:{result.value}")else:print("Operation failed:")ifresult.detail.is_instance_of(ValidationError):print("Ooo! This is a validation error!")print(result.detail)For better error management, you can also specify the error type.You can usethe implementederror types or
implement your own error type.Sample 3: Custom Error DetailfromtypingimportOptionalfromon_railsimportResult,def_result,ErrorDetailclassCustomErrorDetail(ErrorDetail):custom_field:str="custom field!"def__init__(self,message:Optional[str]=None):super().__init__(title="This is my custom detail",message=message)self.code=600# Custom error codedef__str__(self):error_details=super().__str__()error_details+=f"Custom Field:{self.custom_field}"returnerror_details@def_result()defdivide_numbers(a:int,b:int):ifb==0:returnResult.fail(CustomErrorDetail(message="Cannot divide by zero"))returnResult.ok(a/b)Sample 4: Retry Operationsfromon_rails.ResultDetails.SuccessimportCreatedDetailfromon_railsimportResult,def_resultimportrequests@def_result()defcreate_data(url:str,data:dict[str,str])->Result:response=requests.post(url,data=data,timeout=2000)response.raise_for_status()# Raise an exception if the status code indicates an errordetail=CreatedDetail()ifresponse.status_code==201elseNonereturnResult.ok(response.json(),detail)deffake_operation():returnResult.ok()fake_operation().on_success(lambda:create_data(url,data),num_of_try=5)In the example above, if the request goes wrong, anexceptionwill be raised. By settingnum_of_try, you can
specify how many times the operation should be repeated in case of an error.Sample 5: Async DecoratorBy default, all operations are executedsynchronous. If you want to beasynchronoussetis_asyncto true.fromon_railsimportdef_result@def_result(is_async=True)asyncdeffetch(session,url):asyncwithsession.get(url)asresponse:returnawaitresponse.text()Note: In asymmetric mode,chainof functions is not supported.CHANGELOGPlease see theCHANGELOGfile.FeaturesEasy to use:on_railsis designed to be simple and easy to use, with a minimal API and clear documentation.Compatibility with existing code:on_railscan be easily added to existing codes without the need for major
refactoring. You can use decorator for wrap old functions or write new functions without worrying about
incompatibilities.Save any details you like:Thanks to
theResultDetailclass, you can store
various information about the output of the function. Also, by inheriting from this class, you can write new and
customized classes for your project.Special details for errors:With
theErrorDetailclass, you can
store specific details about errors. For example, this class supportsstack tracein a built-in way.Support for common details by default:Inthis link, you can see the different types
of details that are supported.RoadmapSee theopen issuesfor a list of proposed features (and known issues).Top Feature Requests(
Add your votes using the 👍 reaction)Top Bugs(
Add your votes using the 👍 reaction)Newest BugsSupportReach out to the maintainers at one of the following places:GitHub issuesFAQDo I need rewrite all the functions in a new way?not necessarily.You can add this library and write new functions without changing the previous codes.Also for old functions, you can usedecorator. By using decorator, The output of the function is converted
toResultformat. This way, your code is wrap in atry-exceptblock to handle all exceptions.How to manage all function exceptions?By using decorator, your code is wrap in atry-exceptblock and the final output is converted to Result. In this way,
all exceptions are handled.Project assistanceIf you want to say thank you or/and support active development ofon_rails:Add aGitHub Starto the project.Write interesting articles about the project onDev.to,Mediumor your
personal blog.Together, we can makeon_railsbetter!ContributingFirst off, thanks for taking the time to contribute! Contributions are what make the free/open-source community such an
amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and aregreatly
appreciated.Please readour contribution guidelines, and thank
you for being involved!Please do not forget that this project usesconventional commits, so please follow the specification in your commit messages. You can see valid types fromthis file.Authors & contributorsThe original setup of this repository is byPayadel.For a full list of all authors and contributors,
seethe contributors page.Securityon_railsfollows good practices of security, but 100% security cannot be assured.on_railsis provided"as is"without anywarranty.For more information and to report security issues, please refer to
oursecurity documentation.LicenseThis project is licensed under theGPLv3.SeeLICENSEfor more information.