lcdp-fastapi-utils 1.5.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: lcdp-fastapi-utils
3
+ Version: 1.5.8
4
+ Summary: FastApi Utils
5
+ Author: Le Comptoir Des Pharmacies
6
+ Author-email: webmaster@lecomptoirdespharmacies.fr
7
+ Requires-Python: >=3.12
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: uvicorn (==0.34.2)
@@ -0,0 +1,74 @@
1
+ import asyncio
2
+ import functools
3
+ import logging
4
+ from concurrent.futures import ThreadPoolExecutor
5
+ from typing import Callable, Any, TypeVar, Coroutine
6
+
7
+ log = logging.getLogger(__name__)
8
+
9
+ T = TypeVar('T')
10
+
11
+
12
+ def background_task(
13
+ interval: int,
14
+ name: str = None,
15
+ max_workers: int = 0,
16
+ ) -> Callable[[Callable[..., Coroutine[Any, Any, T]]], Callable[..., Coroutine[Any, Any, None]]]:
17
+ """
18
+ Decorator that turns a coroutine into a background task
19
+ with proper cancellation handling for a graceful shutdown.
20
+
21
+ Args:
22
+ interval: Interval in seconds between each execution
23
+ name: Name of the task for logging (optional)
24
+ max_workers: Maximum number of workers. 0 means no ThreadPoolExecutor
25
+
26
+ Returns:
27
+ The decorated function that runs at regular intervals
28
+ """
29
+
30
+ def decorator(func: Callable[..., Coroutine[Any, Any, T]]) -> Callable[..., Coroutine[Any, Any, None]]:
31
+ task_name = name or func.__name__
32
+
33
+ @functools.wraps(func)
34
+ async def wrapper(*args: Any, **kwargs: Any) -> None:
35
+ try:
36
+ if max_workers > 0:
37
+ with ThreadPoolExecutor(max_workers=max_workers) as pool:
38
+ try:
39
+ kwargs['pool'] = pool
40
+ await _run_task_loop(func, task_name, interval, *args, **kwargs)
41
+ finally:
42
+ # When task is finished (normal or cancelled by SIGTERM) then shutdown the pool and cancel any futures
43
+ pool.shutdown(wait=False, cancel_futures=True)
44
+ else:
45
+ await _run_task_loop(func, task_name, interval, *args, **kwargs)
46
+ except asyncio.CancelledError:
47
+ log.info(f"{task_name} cancelled, shutting down gracefully")
48
+ finally:
49
+ log.info(f"{task_name} shut down complete")
50
+
51
+ return wrapper
52
+
53
+ return decorator
54
+
55
+
56
+ async def _run_task_loop(
57
+ func: Callable[..., Coroutine[Any, Any, T]],
58
+ task_name: str,
59
+ interval: int,
60
+ *args: Any,
61
+ **kwargs: Any
62
+ ) -> None:
63
+ """Runs the function in a loop with cancellation handling."""
64
+ while True:
65
+ try:
66
+ await func(*args, **kwargs)
67
+ except Exception:
68
+ log.exception(f"Error in {task_name}")
69
+
70
+ try:
71
+ await asyncio.sleep(interval)
72
+ except asyncio.CancelledError:
73
+ log.info(f"{task_name} received cancellation signal, shutting down gracefully")
74
+ break
@@ -0,0 +1,23 @@
1
+ [tool.poetry]
2
+ name = "lcdp-fastapi-utils"
3
+ # https://github.com/python-poetry/poetry/issues/1208
4
+ version = "1.5.8"
5
+ description = "FastApi Utils"
6
+ authors = ["Le Comptoir Des Pharmacies <webmaster@lecomptoirdespharmacies.fr>"]
7
+
8
+ [tool.poetry-dynamic-versioning]
9
+ enable = false
10
+ vcs = "git"
11
+
12
+ [tool.poetry.requires-plugins]
13
+ poetry-dynamic-versioning = { version = ">=1.0.0,<2.0.0", extras = ["plugin"] }
14
+
15
+ [tool.poetry.dependencies]
16
+ python = ">=3.12"
17
+ uvicorn = "0.34.2"
18
+
19
+ [tool.poetry.dev-dependencies]
20
+
21
+ [build-system]
22
+ requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning>=1.0.0,<2.0.0"]
23
+ build-backend = "poetry_dynamic_versioning.backend"