aailab-tools 0.1.0__py3-none-any.whl

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,3 @@
1
+ from .notify import notify
2
+
3
+ __all__ = ["notify"]
aailab_tools/notify.py ADDED
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+
3
+ import functools
4
+ import inspect
5
+ import os
6
+ import socket
7
+ from typing import Any, Callable, TypeVar, cast
8
+
9
+ import requests
10
+
11
+ F = TypeVar("F", bound=Callable[..., Any])
12
+
13
+
14
+ class NotificationConfigurationError(RuntimeError):
15
+ """Raised when required notification configuration is missing."""
16
+
17
+
18
+ def _machine_id() -> str:
19
+ return os.environ.get("AAILAB_MACHINE_ID") or socket.gethostname()
20
+
21
+
22
+ def _validate_emails(emails: tuple[str, ...]) -> list[str]:
23
+ cleaned = [email.strip() for email in emails if email.strip()]
24
+ if not cleaned:
25
+ raise ValueError("notify() requires at least one email address")
26
+
27
+ invalid = [email for email in cleaned if "@" not in email]
28
+ if invalid:
29
+ raise ValueError(f"invalid email address(es): {', '.join(invalid)}")
30
+
31
+ return cleaned
32
+
33
+
34
+ def _notify_server(task_name: str, status: int | str, emails: list[str]) -> None:
35
+ endpoint = os.environ.get("SERVER_NOTIFY", "").strip()
36
+ api_token = os.environ.get("API_TOKEN", "").strip()
37
+
38
+ if not endpoint:
39
+ raise NotificationConfigurationError("SERVER_NOTIFY is not configured")
40
+ if not api_token:
41
+ raise NotificationConfigurationError("API_TOKEN is not configured")
42
+
43
+ response = requests.post(
44
+ endpoint,
45
+ headers={
46
+ "Content-Type": "application/json",
47
+ "X-API-TOKEN": api_token,
48
+ },
49
+ json={
50
+ "machine_id": _machine_id(),
51
+ "task_name": task_name,
52
+ "status": status,
53
+ "emails": emails,
54
+ },
55
+ timeout=15,
56
+ )
57
+ response.raise_for_status()
58
+
59
+
60
+ def notify(*emails: str) -> Callable[[F], F]:
61
+ """Notify the blade_dashboard server when the decorated function finishes."""
62
+
63
+ recipients = _validate_emails(emails)
64
+
65
+ def decorator(func: F) -> F:
66
+ task_name = func.__qualname__
67
+
68
+ @functools.wraps(func)
69
+ def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
70
+ try:
71
+ result = func(*args, **kwargs)
72
+ except Exception as exc:
73
+ _notify_server(task_name=task_name, status=str(exc), emails=recipients)
74
+ raise
75
+
76
+ _notify_server(task_name=task_name, status=0, emails=recipients)
77
+ return result
78
+
79
+ @functools.wraps(func)
80
+ async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
81
+ try:
82
+ result = await cast(Any, func)(*args, **kwargs)
83
+ except Exception as exc:
84
+ _notify_server(task_name=task_name, status=str(exc), emails=recipients)
85
+ raise
86
+
87
+ _notify_server(task_name=task_name, status=0, emails=recipients)
88
+ return result
89
+
90
+ if inspect.iscoroutinefunction(func):
91
+ return cast(F, async_wrapper)
92
+ return cast(F, sync_wrapper)
93
+
94
+ return decorator
@@ -0,0 +1,53 @@
1
+ Metadata-Version: 2.4
2
+ Name: aailab-tools
3
+ Version: 0.1.0
4
+ Summary: Utilities for interacting with the AAILab blade dashboard.
5
+ Author: AAILab
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/vrbaj/aailab-tools
8
+ Keywords: decorator,notification,email,blade-dashboard
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3 :: Only
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ Requires-Dist: requests>=2.31.0
20
+
21
+ # aailab-tools
22
+
23
+ `aailab-tools` provides a `notify()` decorator that reports the completion status
24
+ of a function to the `blade_dashboard` notification API.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install aailab-tools
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ The decorator reads its configuration from environment variables:
35
+
36
+ - `SERVER_NOTIFY`: full notification endpoint URL, for example
37
+ `http://127.0.0.1:8000/api/notify`
38
+ - `API_TOKEN`: API token expected by `blade_dashboard server`
39
+ - `AAILAB_MACHINE_ID`: optional machine identifier; if omitted, hostname is used
40
+
41
+ ## Usage
42
+
43
+ ```python
44
+ from aailab_tools import notify
45
+
46
+
47
+ @notify("user@example.com", "admin@example.com")
48
+ def run_job():
49
+ return 42
50
+ ```
51
+
52
+ On success, the server receives status `0`. If the function raises an exception,
53
+ the exception text is sent as the status and the exception is re-raised.
@@ -0,0 +1,6 @@
1
+ aailab_tools/__init__.py,sha256=u-c8VLuPZ759xPQyQzuhs1dNX0C1uBAKUQnkoOfCjR8,49
2
+ aailab_tools/notify.py,sha256=9eKTU2QfT0sFAyUEiriIQDb7at2tlFR7lWQaBS0A7LI,2838
3
+ aailab_tools-0.1.0.dist-info/METADATA,sha256=zdJ01UfNWg1qBXnTZj6t64aTTBz7iBVaySkQx32qUP8,1592
4
+ aailab_tools-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
5
+ aailab_tools-0.1.0.dist-info/top_level.txt,sha256=Lv0wE00Ow-NQnHEanG8hnYgjWgc8z1Qh2IvnkCcFesc,13
6
+ aailab_tools-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ aailab_tools