11"""Standalone application class"""
22import asyncio
3- from .signal import Signal
3+ from asyncio import AbstractEventLoop , CancelledError , Task
4+ from logging import Logger
5+ from typing import Any , Callable , Coroutine , Optional
6+
7+ from .aiosignal import Signal
48from .log import fake_logger
59
10+ AppTask = Callable [["StandaloneApplication" ], Coroutine [None , None , None ]]
11+
612
713class StandaloneApplication :
814 """A standalone async application to run"""
915
10- def __init__ (self , * , logger = fake_logger ):
16+ def __init__ (self , * , logger : Logger = fake_logger ,
17+ loop : Optional [AbstractEventLoop ] = None ) -> None :
1118 """Initialize the application to run
1219
1320 :param logger: The logger class to use
@@ -18,100 +25,86 @@ def __init__(self, *, logger=fake_logger):
1825 self ._on_shutdown = Signal (self )
1926 self ._on_cleanup = Signal (self )
2027
21- self ._state = {}
22- self ._loop = None
23- self ._started_tasks = []
24- self .tasks = []
25- self .main_task = None
28+ self ._state : dict [ str , Any ] = {}
29+ self ._loop = loop
30+ self ._started_tasks : list [ Task ] = []
31+ self .tasks : list [ AppTask ] = []
32+ self .main_task : Optional [ AppTask ] = None
2633
27- def __getitem__ (self , key ):
34+ def __getitem__ (self , key ) -> Any :
2835 return self ._state [key ]
2936
30- def __setitem__ (self , key , value ):
37+ def __setitem__ (self , key , value ) -> None :
3138 self ._state [key ] = value
3239
3340 @property
34- def on_startup (self ):
41+ def on_startup (self ) -> Signal :
3542 return self ._on_startup
3643
3744 @property
38- def on_shutdown (self ):
45+ def on_shutdown (self ) -> Signal :
3946 return self ._on_shutdown
4047
4148 @property
42- def on_cleanup (self ):
49+ def on_cleanup (self ) -> Signal :
4350 return self ._on_cleanup
4451
45- async def startup (self ):
52+ async def startup (self ) -> None :
4653 """Trigger the startup callbacks"""
4754 await self .on_startup .send (self )
4855
49- async def shutdown (self ):
56+ async def shutdown (self ) -> None :
5057 """Trigger the shutdown callbacks
5158
5259 Call this before calling cleanup()
5360 """
5461 await self .on_shutdown .send (self )
5562
56- async def cleanup (self ):
63+ async def cleanup (self ) -> None :
5764 """Trigger the cleanup callbacks
5865
5966 Calls this after calling shutdown()
6067 """
6168 await self .on_cleanup .send (self )
6269
6370 @property
64- def loop (self ):
71+ def loop (self ) -> AbstractEventLoop :
72+ if not self ._loop :
73+ self ._loop = asyncio .new_event_loop ()
6574 return self ._loop
6675
67- @loop .setter
68- def loop (self , loop ):
69- if loop is None :
70- loop = asyncio .get_event_loop ()
71-
72- if self ._loop is not None and self ._loop is not loop :
73- raise RuntimeError ("Can't override event loop after init" )
74-
75- self ._loop = loop
76-
77- def start_task (self , func ):
76+ def start_task (self , func : AppTask ) -> Task :
7877 """Start up a task"""
7978 task = self .loop .create_task (func (self ))
8079 self ._started_tasks .append (task )
8180
82- def done_callback (done_task ):
81+ def done_callback (done_task ) -> None :
8382 self ._started_tasks .remove (done_task )
8483
8584 task .add_done_callback (done_callback )
8685 return task
8786
88- def run (self , loop = None ):
89- """Actually run the application
90-
91- :param loop: Custom event loop or None for default
92- """
93- if loop is None :
94- loop = asyncio .get_event_loop ()
95-
96- self .loop = loop
97-
87+ def run (self ) -> None :
88+ """ Actually run the application """
89+ loop = self .loop
9890 loop .run_until_complete (self .startup ())
9991
10092 for func in self .tasks :
10193 self .start_task (func )
10294
95+ def shutdown_exception_handler (_loop : AbstractEventLoop , context : dict [str , Any ]) -> None :
96+ if "exception" not in context or not isinstance (context ["exception" ], CancelledError ):
97+ _loop .default_exception_handler (context )
98+ loop .set_exception_handler (shutdown_exception_handler )
99+
103100 try :
101+ assert self .main_task
104102 task = self .start_task (self .main_task )
105103 loop .run_until_complete (task )
106104 except (KeyboardInterrupt , SystemError ):
107105 print ("Attempting graceful shutdown, press Ctrl-C again to exit" , flush = True )
108106
109- def shutdown_exception_handler (_loop , context ):
110- if "exception" not in context or not isinstance (context ["exception" ], asyncio .CancelledError ):
111- _loop .default_exception_handler (context )
112- loop .set_exception_handler (shutdown_exception_handler )
113-
114- tasks = asyncio .gather (* self ._started_tasks , loop = loop , return_exceptions = True )
107+ tasks = asyncio .gather (* self ._started_tasks , return_exceptions = True )
115108 tasks .add_done_callback (lambda _ : loop .stop ())
116109 tasks .cancel ()
117110
0 commit comments