Package: atila
AtilaAtilaislife-cycle hook based web frameworkwhich is run onSkitai WSGI App Engine.# myservice/__init__.pydef__app__():fromatilaimportAtilareturnAtila(__name__)def__mount__(context,app):@app.route("/")defindex(context):return"Hello, World"# skitaid.pyimportskitaiimportmyserviceskitai.mount("/",myservice)skitai.run()And run,python3skitaid.pyNow,http://localhost:5000/is working.Life-Cycle Hook Based ImplementationUsers can use some special hook functions like__app__ (),__mount__ ()and so on.This hooks can be integrated your existing source codes without any side effects.
You just add routed controllers and then it could work as API backend.Important NoticeCAUTION: Atila is base on WSGI but can be run only
withSkitai App Enginebecause it crosses the border of WSGI for async router.This means if you make your Atila app, you have no choice
but Skitai as WSGI app server. And Atila's unique and unconventional
style may become very hard work to port to other framework.Async/Await SupportAtila almost fully supportasync/awaitmanner.Briefly Atila has 2 event loops for:handling requests withasyncoreas main looprunning async functions withasyncioas executorI still recommend usesyncfuntions mainly unless massive
I/O related tasks or have no choice.Table of ContentAtilaLife-Cycle Hook Based ImplementationImportant NoticeAsync/Await SupportInstallationBefore You StartGetting IDE SuggestionsQuick StartDumb SeekUnit TestingWrite Atila App HooksAdd Launch ScriptAdd Analyzing API EndpointAdd Indexing and Searching API EndpointsTesting APIFinal Source CodesConclusionConnecting DatabaseOption I: Database Management Not RequiredOption II: Database Management Required: Django As ORMInitialize Django ProjectAuto Reloading For DevelopmentAdd Django AppDjango Rest FrameworkWorking With Atila AppConclusionApp Life Cycle and HooksApp Life Cycle and HooksRuntine Preference and BootstrappingRequest Life Cycle and HooksContextGlobal ContextProto ContextThread ContextRequest ContextRequest ContextCloned ContextRegistering Context Scope Objects and MethodsRoutingRoutingURL Parameter TypesString (Default)IntegerFloatPathRequest ParametersAsync RoutingRequest ParametersURL / Body ParameterParameter ValidationInline ValidationClass ValidationDjango Model Based ValidationFunctional ValidationMore ValidatorsValidation OperatorsGetting Argument SpecificationsProcessing RequestRequest ObjectBasic MembersBasic MethodsRoute OptionsEnvironment VariablesIn The Template EngineApp & Request GloabalFile UploadCookieSessionNamespaced SessionMessage BoxRoute Name Based CallHelpersConditional PreworkChecking DependenciesMaking ResponseCache ControlHTTP ErrorPrimitiveStringAPI ResponseRendered Templaterender_or_APIFileStaticGeneratorRedirectingRPC ResponseThreaded Data StreamingBuilding URLWebsocketSpecificationsWS_STREAMWS_SESSIONOpening/Closing HooksWebSocket Piped ProcessgRPCUnary RPCAsync versionAsync Streaming RPCResponse StreamingRequest StreamingBidirectional StreamingAccess Control and AuthenticationCORS (Cross Origin Resource Sharing) and PreflightCustom AuthenticationWWW-AuthenticationAuthentication On Specific MethodsPassword ProviderAuthentication On Entire AppBearer AuthenticationTest PassingSecurity and TokenCross Site Request Forgery Token (CSRF Token)JWT TokenOne-Time PasswordOne-Time TokenEvent BusRequest Life Cycle EventsInterval Base App MaintenancingMultiple GPUs Allocation To WorkersUsing TaskThread / Process / SubrocessTasksHTTP/2,3 Server PushingLogging and TracebackTemplate EngineCustomizing Jinja2Custom Error TemplatesUsing RequestsAllied AppConclusionWorking With Multiple AppsEvent SubscriptionData ExchangingAccesing Other App DirectlyTest ClientIntegrating pytest and API DocumentationDebuggingVS CodeDeploymentUsing SystemctlUsing AWS ECS (Elastic Container Service)Change LogInstallationRequirementsPython 3.7+InstallationAtila and other core base dependent libraries is developing on
single milestone, install/upgrade all at once. Otherwise it is
highly possible to meet some errors.With pippip3install-Uatilaskitairs4Optional required as you need,pip3installprotobuf# for GRPC<< Back To READMEBefore You StartGetting IDE SuggestionsType hinting enables IDE suggestions.fromatilaimportAtila,Contextdef__mount__(context:Context,app:Atila):@app.route("/ping")defping(context:Context):return'pong'In this document, type hinting is not used for code readability.Quick StartFor exmaple, you make local dumb search engine named asdumbseek.Dumb SeekYour package structure is like this.dumbseek/index/__init__.pyindexer.pysearcher.pydb.py__init__.pyanalyzer.pyFiledumbseek/__init__.py__version__="1.0"NAME="Dumb Seek"Filedumbseek/analyzer.pydefanalyze(query):returnquery.lower().split()Filedumbseek/index/__init__.pyisempty.Filedumbseek/index/db.pyINVERTED_INDEX={}DOCUMENTS={}Filedumbseek/index/indexer.pyfrom..importanalyzerfrom.importdbdefindex(doc):doc_ids=list(db.DOCUMENTS.keys())ifnotdoc_ids:doc_id=0else:doc_id=max(doc_ids)+1db.DOCUMENTS[doc_id]=docfortokeninanalyzer.analyze(doc):iftokennotindb.INVERTED_INDEX:db.INVERTED_INDEX[token]=set()db.INVERTED_INDEX[token].add(doc_id)returndoc_idFiledumbseek/index/searcher.pyfrom..importanalyzerfrom.importdbdefsearch(query):results=Nonefortokeninanalyzer.analyze(query):iftokennotindb.INVERTED_INDEX:return[]doc_ids=db.INVERTED_INDEX.get(token,set())ifresultsisNone:results=doc_idscontinueresults=results.intersection(doc_ids)return[db.DOCUMENTS[doc_id]fordoc_idinsorted(list(results))]Unit TestingFor more clarify, pytest example is:importosimportsys;sys.path.insert(0,'../examples/dumbseek')importdumbseekfromdumbseekimportanalyzerfromdumbseek.indeximportindexer,searcher,dbdeftest_analyze():assertanalyzer.analyze('Detective Holmes')==['detective','holmes']deftest_index():assertindexer.index('Detective Holmes')==0assertdb.DOCUMENTS[0]=='Detective Holmes'assertdb.INVERTED_INDEX['detective']=={0}assertindexer.index('Detective Monk')==1assertdb.INVERTED_INDEX['monk']=={1}assertsearcher.search('detective holmes')==['Detective Holmes']Write Atila App HooksNow, you finddumbseekis useful than you think, you have plan to
serve with RESTful API.Add__app__and__mount__hooks intodumbseek/__init__.py.__version__="1.0"NAME="Dumb Seek"# atila hooks ------------------------def__app__():fromatilaimportAtilareturnAtila(__name__)def__mount__(context,app):@app.route("/")defindex(context):returncontext.API(app=NAME)Add Launch ScriptFor testing, you have to create launch script.# skitaid.pyimportskitaiimportdumbseekif__name__=='__main__':withskitai.preference()aspref:skitai.mount('/',dumbseek,pref)skitai.run(port=5000,name='dumbseek')python3skitaid.py--develNow,http://localhost:5000/is working.Add Analyzing API EndpointWe have to create endpointPOST /api/tokens.
Add__mount__hook todumbseek/analyzer.py.defanalyze(query):returnquery.lower().split()# atila hooks ------------------------def__mount__(context,app):@app.route("/tokens",methods=["POST","OPTIONS"])deftokens(context,query):returncontext.API(result=analyze(query))And for mounting to app, add__setup__hook todumbseek/__init__.py.def__setup__(context,app):from.importanalyzerapp.mount("/api",analyzer)def__mount__(context,app):...http://localhost:5000/api/tokensis working.Add Indexing and Searching API EndpointsWe have to create 3 endpoints:POST /api/documentsfor indexing documentGET /api/documentsfor searching documentGET /api/documents/<int:doc_id>for geting documentAdd__mount__todumbseek/index/__init__.py.# atila hooks ------------------------def__mount__(context,app):from.importindexerfrom.importsearcher@app.route("/documents",methods=["POST","OPTIONS"])defindex_document(context,document):doc_id=indexer.index(document)returncontext.API("201 Created",url=context.urlfor(get_document,doc_id))@app.route("/documents",methods=["GET"])defsearch_document(context,q):returncontext.API(result=searcher.search(q))@app.route("/documents/<int:doc_id>",methods=["GET"])defget_document(context,doc_id):try:returncontext.API(document=db.DOCUMENTS[doc_id])exceptKeyError:raisecontext.HttpError("404 Not Found")And mount to app, add__setup__hook todumbseek/__init__.py.def__setup__(context,app):from.importanalyzerfrom.importindexapp.mount("/api",analyzer)app.mount("/api",index)def__mount__(context,app):...http://localhost:5000/api/documentsis working.Testing APIimportpytestfromfunctoolsimportpartialimportskitaifromatila.pytest_hooksimport*@pytest.fixturedeflaunch():returnpartial(skitai.test_client,port=30371,silent=False)deftest_api(launch):withlaunch('../examples/dumbseek/skitaid.py')asengine:r=engine.get('/')assertr.json()['app']=='Dumb Seek'r=engine.post('/api/tokens',data={'query':'Detective Holmes'})assertr.json()['result']==['detective','holmes']r=engine.post('/api/documents',data={'document':'Detective Holmes'})assertr.status_code==201assertr.json()['url']=='/api/documents/0'r=engine.get('/api/documents',params={'q':'Detective Holmes'})assertr.json()['result']==['Detective Holmes']r=engine.get('/api/documents/0')assertr.json()['document']=='Detective Holmes'r=engine.post('/api/documents',data={'document':'Detective Monk'})r=engine.get('/api/documents',params={'q':'detective'})assertr.json()['result']==['Detective Holmes','Detective Monk']Final Source CodesSeehttps://gitlab.com/skitai/atila/-/tree/master/examples/dumbseekConclusionWe add REST API to our existing source codes without any side effectsWe can still usedumbseekas local libraryJust addskitaid.py, we can serve as RESTful API online<< Back To README