Skip to content

Commit 3c94dbf

Browse files
committed
chore: Make diverse type checkers happy
Signed-off-by: Kai Blin <kblin@biosustain.dtu.dk>
1 parent 21f7212 commit 3c94dbf

File tree

9 files changed

+77
-69
lines changed

9 files changed

+77
-69
lines changed

aiostandalone/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
__version__ = '0.1.0'
22

3-
from aiostandalone.app import StandaloneApplication
3+
from aiostandalone.app import StandaloneApplication # noqa
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,17 @@ class Signal(list):
99
"""
1010
__slots__ = ('_app', )
1111

12-
def __init__(self, app):
12+
def __init__(self, app) -> None:
1313
"""Connect the signal to the app
1414
1515
:param app: app the signal is conencted to
1616
"""
1717
super().__init__()
1818
self._app = app
1919

20-
async def send(self, *args, **kwargs):
20+
async def send(self, *args, **kwargs) -> None:
2121
"""Send args and kwargs to all registered callbacks"""
2222
for callback in self:
2323
res = callback(*args, **kwargs)
2424
if asyncio.iscoroutine(res) or isinstance(res, asyncio.Future):
2525
await res
26-

aiostandalone/app.py

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
"""Standalone application class"""
22
import 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
48
from .log import fake_logger
59

10+
AppTask = Callable[["StandaloneApplication"], Coroutine[None, None, None]]
11+
612

713
class 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

aiostandalone/log.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""Fake logger configuration"""
2+
from logging import Logger
23

34

4-
class FakeLogger:
5+
class FakeLogger(Logger):
56
"""Fake logger that simply discards all logs"""
67

78
def debug(self, msg, *args, **kwargs):
@@ -25,4 +26,4 @@ def critical(self, msg, *args, **kwargs):
2526
pass
2627

2728

28-
fake_logger = FakeLogger()
29+
fake_logger = FakeLogger("FakeLogger")

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
max-line-length = 100

setup.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
if os.path.exists('README.rst'):
99
long_description = open('README.rst').read()
1010

11-
install_requires = [
12-
]
11+
install_requires: list[str] = []
1312

1413

1514
tests_require = [
@@ -61,4 +60,3 @@ def run_tests(self):
6160
'testing': tests_require,
6261
},
6362
)
64-

tests/test_app.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async def fake_main_task(app):
3333

3434

3535
def test_run(loop):
36-
app = StandaloneApplication()
36+
app = StandaloneApplication(loop=loop)
3737

3838
app.on_startup.append(fake_startup)
3939
app.on_shutdown.append(fake_shutdown)
@@ -50,20 +50,15 @@ def test_run(loop):
5050
assert app['ran_main_task']
5151

5252

53-
def test_loop():
54-
loop = asyncio.get_event_loop()
55-
app = StandaloneApplication()
56-
app.loop = loop
57-
58-
assert app._loop == loop
59-
60-
app.loop = None
53+
def test_loop(loop):
54+
app = StandaloneApplication(loop=loop)
55+
assert app.loop == loop
6156

62-
with pytest.raises(RuntimeError):
63-
app.loop = 23
57+
app = StandaloneApplication()
58+
assert app.loop and app.loop != loop
6459

6560

66-
async def fake_failing_task(_):
61+
async def fake_aborting_task(_):
6762
await asyncio.sleep(0.01)
6863
raise SystemError
6964

@@ -73,14 +68,34 @@ async def long_running_side_task(_):
7368

7469

7570
def test_abort(loop):
76-
app = StandaloneApplication()
71+
app = StandaloneApplication(loop=loop)
72+
app.on_startup.append(fake_startup)
73+
app.on_shutdown.append(fake_shutdown)
74+
app.on_cleanup.append(fake_cleanup)
75+
76+
app.main_task = fake_aborting_task
77+
app.tasks.append(long_running_side_task)
78+
79+
app.run()
80+
assert app['ran_shutdown']
81+
assert app['ran_cleanup']
82+
83+
84+
async def fake_failing_task(_):
85+
await asyncio.sleep(0.01)
86+
raise RuntimeError("Ruh roh")
87+
88+
89+
def test_exception(loop):
90+
app = StandaloneApplication(loop=loop)
7791
app.on_startup.append(fake_startup)
7892
app.on_shutdown.append(fake_shutdown)
7993
app.on_cleanup.append(fake_cleanup)
8094

8195
app.main_task = fake_failing_task
8296
app.tasks.append(long_running_side_task)
97+
with pytest.raises(RuntimeError, match="Ruh roh"):
98+
app.run()
8399

84-
app.run(loop)
85100
assert app['ran_shutdown']
86101
assert app['ran_cleanup']

tests/test_logger.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44

55
def test_object():
6-
fake = FakeLogger()
6+
fake = FakeLogger("Fake")
77
fake.debug("Nope")
88
fake.info("Nope")
99
fake.warning("Nope")

tests/test_signal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from collections import defaultdict
33
import pytest
44

5-
from aiostandalone.signal import Signal
5+
from aiostandalone.aiosignal import Signal
66

77

88
def test_init():

0 commit comments

Comments
 (0)