Package: facet
FacetService manager for asyncio (and classic blocking code since version 0.10.0).FacetReasonsAsyncioBlocking codeFeaturesLicenseRequirementsUsageAsyncioBlocking codeAPIAsynciostartstopdependenciesadd_taskrunwaitgraceful_shutdown_timeoutrunningBlocking codestartstopdependenciesrunningReasonsAsynciomodetries to do too much job:Messy callbacks (on_start,on_started,on_crashed, etc.).Inheritance restrict naming and forcessuper()calls.Forced logging module and logging configuration.Blocking codeExitStackis too low-level to manage services.Common api for async and blocking worlds.FeaturesSimple (start,stop,dependenciesandadd_task).Configurable via inheritance (graceful shutdown timeout).Mixin (nosuper()required).Requires no runner engine (Worker,Runner, etc.) just plainawaitorasync with/with.Licensefacetis offered under MIT license.Requirementspython 3.11+Last version with python 3.6+ support is 0.9.1UsageAsyncioimportasynciofromfacetimportAsyncioServiceMixinclassB(AsyncioServiceMixin):def__init__(self):self.value=0asyncdefstart(self):self.value+=1print("b started")asyncdefstop(self):self.value-=1print("b stopped")classA(AsyncioServiceMixin):def__init__(self):self.b=B()@propertydefdependencies(self):return[self.b]asyncdefstart(self):print("a started")asyncdefstop(self):print("a stopped")asyncio.run(A().run())This will produce:b started
a startedStart and stop order determined by strict rule:dependencies must be started first and stopped last. That is whyBstarts beforeA. SinceAmay useBinstartroutine.Hitctrl-cand you will see:a stopped
b stopped
Traceback (most recent call last):
  ...
KeyboardInterruptStop order is reversed, sinceAmay useBinstoproutine. Any raised exception propagates to upper context.facetdo not trying to be too smart.Service can be used as a context manager. Instead ofasyncio.run(A().run())Code can look like:asyncdefmain():asyncwithA()asa:asserta.b.value==1awaita.wait()asyncio.run(main())Another service feature isadd_taskmethod:classA(AsyncioServiceMixin):asyncdeftask(self):awaitasyncio.sleep(1)print("task done")asyncdefstart(self):self.add_task(self.task())print("start done")asyncio.run(A().run())This will lead to background task creation and handling:start done
task doneAny non-handled exception on background task will lead the whole service stack crashed. This is also a key feature to fall down fast and loud.All background tasks will be cancelled and awaited on service stop.You can manage dependencies start/stop to start sequently, parallel or mixed. Like this:classA(AsyncioServiceMixin):def__init__(self):self.b=B()self.c=C()self.d=D()@propertydefdependencies(self):return[[self.b,self.c],self.d,]This leads to firstbandcstarts parallel, after they successfully starteddwill try to start, and thenaitself start will be called. And on stop routineastop called first, thendstop, then bothbandcstops parallel.The rule here isfirst nesting level is sequential, second nesting level is parallelBlocking codeSince version 0.10.0facetcan be used in blocking code with pretty same rules.But with limited API. For example:fromfacetimportBlockingServiceMixinclassB(BlockingServiceMixin):def__init__(self):self.value=0defstart(self):self.value+=1print("b started")defstop(self):self.value-=1print("b stopped")classA(BlockingServiceMixin):def__init__(self):self.b=B()@propertydefdependencies(self):return[self.b]defstart(self):print("a started")defstop(self):print("a stopped")withA()asa:asserta.b.value==1This will produce:b started
a started
a stopped
b stoppedAs you can see, there is nowaitmethod. Waiting and background tasks are on user shoulders and technically can be implemented withconcurrent.futuresmodule. Butfacetdo not provide such functionality, since there are a lot of ways to do it:threading/multiprocessingand their primitives.Also, there are no «sequential, parallel and mixed starts/stops for dependencies» feature. So, just put dependencies independenciesproperty as a plainlistand they will be started/stopped sequentially.APIAsyncioHere is public methods you get on inheritance/mixin:startasyncdefstart(self):passStart routine.stopasyncdefstop(self):passStop routine.dependencies@propertydefdependencies(self)->list[AsyncioServiceMixin|list[AsyncioServiceMixin]]:return[]Should return iterable of current service dependencies instances.add_taskdefadd_task(self,coroutine:Coroutine[Any,Any,Any])->asyncio.Task[Any]:Add background task.runasyncdefrun(self)->None:Run service and wait until it stop.waitasyncdefwait(self)->None:Wait for service stop. Service must be started. This is useful when you use service as a context manager.graceful_shutdown_timeout@propertydefgraceful_shutdown_timeout(self)->int:return10How much total time in seconds wait for stop routines. This property can be overriden with subclass:classCustomServiceMixin(AsyncioServiceMixin):@propertydefgraceful_shutdown_timeout(self):return60running@propertydefrunning(self)->bool:Check if service is runningBlocking codestartdefstart(self):passStart routine.stopdefstop(self):passStop routine.dependencies@propertydefdependencies(self)->list[BlockingServiceMixin|list[BlockingServiceMixin]]:return[]Should return iterable of current service dependencies instances.running@propertydefrunning(self)->bool:Check if service is running