Package: service-flow
service-flowA small, simple, and elegant component/function orchestration framework for Python,service-flowenables separation of a functionality into single-responsibility and bite-size functions/service objects, which are testable and reuseable. It further helps with readability and maintainability of the embedding application. It supports both sync and async services.Implementationflowbasic example (>>)fork example (<)servicesexamplebasic servicedecorator serviceasync servicelambda serviceconventionsinitialization parametersfunction parametersreturn valueexceptionsinspirationTODOsinstallImplementationservice-flowhas two parts -- aflowand multipleservices(Note: the wordservicehere refers to a function, not RESTful services):flowa flow is the definition of processing procedure. It is defined as the following:basic example (>>)flow=Service1()>>\Service2(*args)>>\...ServiceN(**kwargs)output=flow(input)## input is a dictionary with all the input parameters as attributesPython>>operator is overloaded to define the sequence of the processing.Service1(), ...,ServiceN()are instances of the services, or functions.for each processing of the input,service-flowcreates acontext, a dictionary-like object that serves as input and gather outputs from all the services.fork example (<)flow=Service1()>>\Service2(*args)<\('context_var_name',{'var_value1':(Service3()>>Service4(**kwargs)),'var_value2':(Service5()>>Service4(**kwargs)),})output=flow(input)## input is a dictionary with all the input parameters as attributesPython<operator is overloaded to define a fork in processing. In this example,context_var_nameis the name of context key, andvar_value1,var_value2are potential values for forking.A fork can not be the first service in a flow definition.servicesA service is the equivalent of a Python function.examplebasic serviceA element in a list of services that composes the flow. it inherits fromservice_flow.middleware.Middleware.classInplaceModification(Middleware):def__init__(self,increment):# initializationself.increment=1def__call__(self,bar:list):# service callreturn{'bar':[i+self.incrementforiinbar]}decorator serviceAs the name implies, decorator service works similiarly to a python decorator that is nested on top of the subsequent flow.A decorator service inherits fromservice_flow.middleware.DecoratorMiddlewareand has one additional instance variableapp, which is the reference to the subsequent flow.classExceptionHandler(DecoratorMiddleware):def__call__(self,context):try:self.app(context)exceptZeroDivisionError:return{'error':'decided by zero'}async serviceAs asyncio was introduced in python version 3.4,service-flownow supports async middlewares. here is example:importaiohttpclassGetPythonSiteHTML(Middleware):def__init__(self,increment):self.increment=incrementasyncdef__call__(self,bar:list):asyncwithaiohttp.ClientSession()assession:asyncwithsession.get('http://python.org')asresponse:html=awaitresponse.text()returnawait{'response':html}lambda serviceservice-flowsupports simple functionality as a service in the form a lambda type. Note the following:a lambda service is always synca lambda service can not be the first service on stack.conventionsIt needs to follow the convention below:initialization parametersparameters that initializes the service and is shared for all the calls to the service.function parametersThese parameters are used as inputs to a service. They normally have to be existing attributes in the context object.The only exception is if there is only one parameter and its name is "context", in which case the entire context dictionary will be passed in as the value of the context parameter.return valuethe return value of a service is optional. If a service does return values:if it is a dictionary, it will add/update the aforementioned processingcontext.otherwise, the return value is ignored and a warning message is logged.exceptionsservice-flowraises a few types of exceptions:StopFlowException: raised inside a service to signal stop processing. typical use cases include when incoming request is invalid.RetryException: raised inside a service to signal re-processing of the same request. typical use cases include when a http request issued from the service times out or database transaction level violation.FatalException: raised inside a service to restart the processor. typical use cases include when database connection is broken or other infrastructure related errors.inspirationservice-flowdraws inspiration from the following Ruby projects:RackLight ServiceRuby MiddlewareTODOsinstallpip install service-flow