Skip to content

Commit f6da742

Browse files
committed
implemented simple background tasks execution
1 parent 6686405 commit f6da742

2 files changed

Lines changed: 152 additions & 20 deletions

File tree

background_tasks.py

Lines changed: 0 additions & 20 deletions
This file was deleted.

simple_background_tasks.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
3+
'''
4+
Copyright (c) 2024 Godwin Peter .O
5+
6+
Licensed under the MIT License
7+
you may not use this file except in compliance with the License.
8+
https://opensource.org/license/mit
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
15+
Author: Godwin peter .O (me@godwin.dev)
16+
Created At: Saturday, 7th Dec 2024
17+
Modified By: Godwin peter .O
18+
Modified At: Sat Dec 07 2024
19+
'''
20+
21+
import asyncio
22+
from typing import TypedDict
23+
import logging
24+
25+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
26+
logger = logging.getLogger(__name__)
27+
28+
# -----------------------------------------------
29+
# Task definitions
30+
# -----------------------------------------------
31+
32+
async def task_one(val: int):
33+
"""
34+
Logs a message with the provided integer value.
35+
36+
Args:
37+
val (int): An integer value used in the log message.
38+
39+
Example:
40+
Task one-2 ticker...
41+
"""
42+
logger.info("Task one-{0} ticker...".format(val))
43+
44+
async def task_two():
45+
"""
46+
Logs a simple message indicating that the task is running.
47+
48+
Example:
49+
Task two ticker...
50+
"""
51+
logger.info("Task two ticker...")
52+
53+
TimerTask = TypedDict("TimerTask", {"interval": float, "task": any, "arg1": any })
54+
55+
timers = []
56+
all_tasks: list[TimerTask] = [
57+
{ "interval": 4, "task": task_one, "arg1": 2 },
58+
{ "interval": 3, "task": task_two, "arg1": None }
59+
]
60+
61+
class Timer:
62+
"""
63+
Represents a recurring asynchronous task that runs at a defined interval.
64+
65+
Attributes:
66+
event_loop (asyncio.AbstractEventLoop): The asyncio event loop managing the task.
67+
interval_sec (float): The interval, in seconds, at which the task runs.
68+
callback (Callable): The function to execute.
69+
args (tuple): Positional arguments for the task.
70+
kwargs (dict): Keyword arguments for the task.
71+
72+
Methods:
73+
cancel():
74+
Cancels the scheduled task.
75+
"""
76+
def __init__(self, event_loop: asyncio.AbstractEventLoop, interval_sec: float, callback, *args, **kwargs):
77+
"""
78+
Initializes the Timer instance.
79+
80+
Args:
81+
event_loop (asyncio.AbstractEventLoop): The event loop to run the task in.
82+
interval_sec (float): Interval in seconds between task executions.
83+
callback (Callable): The task function to execute.
84+
*args: Positional arguments for the callback.
85+
**kwargs: Keyword arguments for the callback.
86+
"""
87+
self._event_loop = event_loop
88+
self._interval_sec = interval_sec
89+
self._callback = callback
90+
self._args = args
91+
self._kwargs = kwargs
92+
self._task = self._event_loop.create_task(self._job())
93+
94+
async def _job(self):
95+
"""
96+
The main job loop that executes the callback at the defined interval.
97+
"""
98+
while True:
99+
await asyncio.sleep(self._interval_sec)
100+
try:
101+
await self._callback(*self._args, **self._kwargs)
102+
except Exception as e:
103+
logger.error(f"Error in task: {e}")
104+
105+
106+
def cancel(self):
107+
"""
108+
Cancels the running task.
109+
"""
110+
self._task.cancel()
111+
112+
113+
async def main(event_loop: asyncio.AbstractEventLoop):
114+
"""
115+
Initializes and starts all tasks defined in the `all_tasks` list.
116+
117+
Args:
118+
event_loop (asyncio.AbstractEventLoop): The asyncio event loop.
119+
120+
Raises:
121+
ValueError: If task definitions in `all_tasks` are invalid.
122+
"""
123+
124+
global timers
125+
for task_props in all_tasks:
126+
args = (task_props["arg1"],) if task_props["arg1"] is not None else ()
127+
timers.append(Timer(event_loop, task_props["interval"], task_props["task"], *args))
128+
129+
await asyncio.sleep(2)
130+
logger.info("Finshed initializing tasks...")
131+
132+
133+
if __name__ == "__main__":
134+
"""
135+
Entry point for the script. Sets up the event loop and starts the task scheduler.
136+
Handles graceful shutdown on keyboard interrupt.
137+
"""
138+
try:
139+
event_loop = asyncio.new_event_loop()
140+
asyncio.set_event_loop(event_loop)
141+
logger.info("Starting main process...")
142+
event_loop.run_until_complete(main(event_loop))
143+
event_loop.run_forever()
144+
except KeyboardInterrupt:
145+
logger.warning('\nCtrl-C (SIGINT) caught. Exiting...')
146+
finally:
147+
for timer in timers:
148+
logger.info(f"Cancelling timer: {timer.name}")
149+
timer.cancel()
150+
if event_loop.is_running():
151+
logger.info("Closing event loop...")
152+
event_loop.close()

0 commit comments

Comments
 (0)