Package: lily
WARNING: this project is still undergoing some heavy changes and is still quite poorly documented so if you're interested in using it, well do that at your own risk.Lily - microservices by humans for humansLily is built around:DDD (Domain Driven Design) = Commands + EventsTDD+ (Test Driven Development / Documentation)FoundationsLily was inspired by various existing tools and methodologies. In order to understand the philosophy ofLilyone must udnerstand two basic concepts:COMMAND- is a thing one can performEVENT- is a consequence of executingCOMMAND(oneCOMMANDcan lead to many events).Inlilywe define commands that are raising (python'sraise) various events that are captured by the main events loop (do not confuse with node.js event loop).Creating HTTP commandsLilyenable very simple and semantic creation of commands using various transport mechanism (HTTP, Websockets, Async) in a one unified way.Each HTTP command is build around the same skeleton:fromlilyimport(command,Meta,name,Input,Output,serializers,Access,HTTPCommands,)classSampleCommands(HTTPCommands):@command(name=<NAME>,meta=Meta(title=<META_TITLE>,description=<META_DESCRIPTION>,domain=<META_DOMAIN>),access=Access(access_list=<ACCESS_LIST>),input=Input(body_parser=<BODY_PARSER>),output=Output(serializer=<SERIALIZER>),)def<HTTP_VERB>(self,request):raiseself.event.<EXPECTED_EVENT>({'some':'thing'})The simplest are HTTP commands that can be defined in the following way:fromlilyimport(command,Meta,name,Input,Output,serializers,Access,HTTPCommands,)classSampleCommands(HTTPCommands):@command(name=name.Read(CatalogueItem),meta=Meta(title='Bulk Read Catalogue Items',domain=CATALOGUE),access=Access(access_list=['ADMIN']),input=Input(body_parser=CatalogueItemParser),output=Output(serializer=serializers.EmptySerializer),)defget(self,request):raiseself.event.Read({'some':'thing'})NamesFIXME: add it ...Creating Authorizer classEachcommandcreated in Lily can be protected from viewers who should not be
able to access it. Currently one can pass to the@commanddecoratoraccess_listwhich is passed to theAuthorizerclass.fromlily.base.eventsimportEventFactoryclassBaseAuthorizer(EventFactory):"""Minimal Authorizer Class."""def__init__(self,access_list):self.access_list=access_listdefauthorize(self,request):try:return{'user_id':request.META['HTTP_X_CS_USER_ID'],'account_type':request.META['HTTP_X_CS_ACCOUNT_TYPE'],}exceptKeyError:raiseself.AccessDenied('ACCESS_DENIED',context=request)deflog(self,authorize_data):returnauthorize_dataBut naturally it can take any form you wish. For example:it could expectAuthorizationheader and performBearertoken decodingit could leverage the existence ofaccess_listallowing one to apply some
sophisticatedauthorizationpolicy.An example of fairly classical (jwt token basedAuthorizerwould be):fromlilyimportBaseAuthorizerfrom.tokenimportAuthTokenclassAuthorizer(BaseAuthorizer):def__init__(self,access_list):self.access_list=access_listdefauthorize(self,request):try:type_,token=request.META['HTTP_AUTHORIZATION'].split()exceptKeyError:raiseself.AuthError('COULD_NOT_FIND_AUTH_TOKEN')else:iftype_.lower().strip()!='bearer':raiseself.AuthError('COULD_NOT_FIND_AUTH_TOKEN')account=AuthToken.decode(token)ifaccount.typenotinself.access_list:raiseself.AccessDenied('ACCESS_DENIED')# -- return the enrichment that should be available as# -- `request.access` attributereturn{'account':account}deflog(self,authorize_data):return{'account_id':authorize_data['account'].id}Notice how above customAuthorizerclass inherits fromBaseAuthorizer.
In order to enable customAuthorizerclass one must set in thesettings.py:LILY_AUTHORIZER_CLASS='account.authorizer.Authorizer'where naturally the module path would depend on a specific project set up.Finally in order to use Authorization at the command level one must set in the @command definition:fromlilyimport(command,Meta,name,Output,serializers,Access,HTTPCommands,)classSampleCommands(HTTPCommands):@command(name=name.Read(CatalogueItem),meta=Meta(title='Bulk Read Catalogue Items',domain=CATALOGUE),access=Access(access_list=['ADMIN']),output=Output(serializer=serializers.EmptySerializer),)defget(self,request):raiseself.event.Read({'some':'thing'})whereaccessentry explicitly specifies who can access a particular command, that list will be injected to theAuthorizeron each request to the server.