ahi-core 0.2.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.
- ahi/core/__init__.py +7 -0
- ahi/core/errors.py +15 -0
- ahi/core/logging.py +30 -0
- ahi/core/polling.py +61 -0
- ahi_core-0.2.0.dist-info/METADATA +32 -0
- ahi_core-0.2.0.dist-info/RECORD +7 -0
- ahi_core-0.2.0.dist-info/WHEEL +4 -0
ahi/core/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Cross-cutting helpers shared across AHI Python libraries."""
|
|
2
|
+
|
|
3
|
+
from ahi.core.errors import AhiError, PollTimeoutError
|
|
4
|
+
from ahi.core.logging import get_logger
|
|
5
|
+
from ahi.core.polling import poll_until
|
|
6
|
+
|
|
7
|
+
__all__ = ["AhiError", "PollTimeoutError", "get_logger", "poll_until"]
|
ahi/core/errors.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Base error types shared across AHI libraries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AhiError(Exception):
|
|
7
|
+
"""Base class for all errors raised by AHI libraries.
|
|
8
|
+
|
|
9
|
+
Catching ``AhiError`` lets callers handle anything this stack raises
|
|
10
|
+
without depending on the concrete subclasses.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PollTimeoutError(AhiError, TimeoutError):
|
|
15
|
+
"""Raised when :func:`ahi.core.polling.poll_until` exceeds its deadline."""
|
ahi/core/logging.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Minimal logging helper.
|
|
2
|
+
|
|
3
|
+
Libraries should not configure global logging; they should obtain a named
|
|
4
|
+
logger and leave handler/level configuration to the consuming application.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
_PACKAGE_ROOT = "ahi"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_logger(name: str | None = None) -> logging.Logger:
|
|
15
|
+
"""Return a logger under the shared ``ahi`` namespace.
|
|
16
|
+
|
|
17
|
+
A :class:`~logging.NullHandler` is attached to the package-root logger so
|
|
18
|
+
that libraries never emit "No handlers could be found" warnings when the
|
|
19
|
+
host application has not configured logging.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
name: Dotted suffix (for example ``"aws.redshift"``). When omitted, the
|
|
23
|
+
package-root logger is returned.
|
|
24
|
+
"""
|
|
25
|
+
root = logging.getLogger(_PACKAGE_ROOT)
|
|
26
|
+
if not any(isinstance(h, logging.NullHandler) for h in root.handlers):
|
|
27
|
+
root.addHandler(logging.NullHandler())
|
|
28
|
+
if not name:
|
|
29
|
+
return root
|
|
30
|
+
return root.getChild(name)
|
ahi/core/polling.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""A small, dependency-free poller for "start then wait for terminal state" APIs.
|
|
2
|
+
|
|
3
|
+
Both the Redshift Data API and Athena follow the same shape: submit a statement,
|
|
4
|
+
then poll a status endpoint until it reaches a terminal state. ``poll_until``
|
|
5
|
+
captures that loop once, with exponential backoff and an injectable clock so it
|
|
6
|
+
stays trivially testable.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import time
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import TypeVar
|
|
14
|
+
|
|
15
|
+
from ahi.core.errors import PollTimeoutError
|
|
16
|
+
|
|
17
|
+
T = TypeVar("T")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def poll_until(
|
|
21
|
+
fetch: Callable[[], T],
|
|
22
|
+
is_done: Callable[[T], bool],
|
|
23
|
+
*,
|
|
24
|
+
timeout: float = 300.0,
|
|
25
|
+
interval: float = 1.0,
|
|
26
|
+
max_interval: float = 30.0,
|
|
27
|
+
backoff: float = 1.5,
|
|
28
|
+
sleep: Callable[[float], None] = time.sleep,
|
|
29
|
+
monotonic: Callable[[], float] = time.monotonic,
|
|
30
|
+
) -> T:
|
|
31
|
+
"""Call ``fetch`` until ``is_done`` returns ``True`` or ``timeout`` elapses.
|
|
32
|
+
|
|
33
|
+
The result is fetched once before the first sleep, so a call that is already
|
|
34
|
+
in a terminal state returns immediately without waiting.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
fetch: Zero-arg callable returning the latest status payload.
|
|
38
|
+
is_done: Predicate deciding whether ``fetch``'s result is terminal.
|
|
39
|
+
timeout: Maximum seconds to wait before raising.
|
|
40
|
+
interval: Initial delay between polls, in seconds.
|
|
41
|
+
max_interval: Upper bound for the (growing) delay.
|
|
42
|
+
backoff: Multiplier applied to the delay after each poll.
|
|
43
|
+
sleep: Sleep function (injectable for tests).
|
|
44
|
+
monotonic: Monotonic clock (injectable for tests).
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
The first ``fetch`` result for which ``is_done`` is ``True``.
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
PollTimeoutError: If the deadline passes before a terminal state.
|
|
51
|
+
"""
|
|
52
|
+
deadline = monotonic() + timeout
|
|
53
|
+
delay = interval
|
|
54
|
+
while True:
|
|
55
|
+
result = fetch()
|
|
56
|
+
if is_done(result):
|
|
57
|
+
return result
|
|
58
|
+
if monotonic() >= deadline:
|
|
59
|
+
raise PollTimeoutError(f"Polling exceeded timeout of {timeout:.1f}s")
|
|
60
|
+
sleep(min(delay, max_interval))
|
|
61
|
+
delay *= backoff
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ahi-core
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Cross-cutting helpers shared across AHI Python libraries (polling, errors, logging).
|
|
5
|
+
Project-URL: Homepage, https://github.com/adaptivehealthintelligence/ahi-python-packages
|
|
6
|
+
Project-URL: Documentation, https://adaptivehealthintelligence.github.io/ahi-python-packages/
|
|
7
|
+
Project-URL: Repository, https://github.com/adaptivehealthintelligence/ahi-python-packages
|
|
8
|
+
Author: Adaptive Health Intelligence
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: ahi,utilities
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# ahi-core
|
|
15
|
+
|
|
16
|
+
Cross-cutting helpers shared across the AHI Python libraries. Deliberately
|
|
17
|
+
dependency-light — it should be safe to depend on from anywhere.
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from ahi.core import get_logger, poll_until, AhiError, PollTimeoutError
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## What's here
|
|
24
|
+
|
|
25
|
+
- `poll_until(...)` — a generic "submit then wait for a terminal state" loop with
|
|
26
|
+
exponential backoff and an injectable clock (used by `ahi-aws`).
|
|
27
|
+
- `get_logger(name)` — a namespaced logger under `ahi.*` with a `NullHandler`
|
|
28
|
+
attached, so libraries never configure global logging.
|
|
29
|
+
- `AhiError` — base exception for everything in the AHI stack;
|
|
30
|
+
`PollTimeoutError` is raised by `poll_until`.
|
|
31
|
+
|
|
32
|
+
Part of [ahi-python-packages](https://github.com/adaptivehealthintelligence/ahi-python-packages).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ahi/core/__init__.py,sha256=h-rpeRSW2W47nMJ1r-0zYr6P9l7ksU-IcA8WNDoxAjk,272
|
|
2
|
+
ahi/core/errors.py,sha256=VtP2ail9QDLaQb__eEmJ2sWdQgoh8wu9q5driB39fqs,437
|
|
3
|
+
ahi/core/logging.py,sha256=cOc30bIJxCxWtn8eUF1z2fFTN2McPLmiYlymVeWbh-8,959
|
|
4
|
+
ahi/core/polling.py,sha256=b3CevYoqLxSxdTqsX1j1_gL2T7KcQB0zmFC6ewS3TV8,2089
|
|
5
|
+
ahi_core-0.2.0.dist-info/METADATA,sha256=vvQZgXmmS_bH1TJMJQP65J39MD_glC6GehRxU9Xesrw,1317
|
|
6
|
+
ahi_core-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
ahi_core-0.2.0.dist-info/RECORD,,
|