rp_python_sdk 1.5.1__tar.gz → 1.5.2__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.
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/PKG-INFO +9 -4
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/README.md +8 -3
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/pyproject.toml +1 -1
- rp_python_sdk-1.5.2/rp_python_sdk/endpoints/util/version_info.py +2 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/relying_party_client_sdk.py +17 -6
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_config.py +1 -1
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_dispatcher.py +34 -0
- rp_python_sdk-1.5.2/rp_python_sdk/telemetry/telemetry_session_pool.py +81 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_token_provider.py +10 -8
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_transport.py +38 -13
- rp_python_sdk-1.5.1/rp_python_sdk/endpoints/util/version_info.py +0 -2
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/__init__.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/cache/__init__.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/cache/http_cache.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/get_participants.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/pushed_authorisation_request.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/retrieve_tokens.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/user_info.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/client_assertion.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/fapi.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/mapping.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/system_info.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/user_agent.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/filters/participant_filters.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/model.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/relying_party_client_sdk_exception.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/sdk_config.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/setup_logger.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/__init__.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/async_telemetry_sender.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/sync_telemetry_sender.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_context.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_event.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_event_factory.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_sender.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_session.py +0 -0
- {rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_url_resolver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: rp_python_sdk
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.2
|
|
4
4
|
Summary: Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem
|
|
5
5
|
License: Proprietary
|
|
6
6
|
Author: Erik Pragt
|
|
@@ -421,7 +421,7 @@ with a unique `x-fapi-interaction-id` that was used for this request to the auth
|
|
|
421
421
|
The SDK emits non-sensitive telemetry about its PAR, Token and UserInfo endpoint interactions to the
|
|
422
422
|
centralized ConnectID telemetry service over mTLS. Telemetry is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
423
423
|
|
|
424
|
-
> **Note:** Telemetry is
|
|
424
|
+
> **Note:** Telemetry is **enabled by default**. To opt out, set `TelemetryConfig(enabled=False)` on your `CustomConfig`.
|
|
425
425
|
|
|
426
426
|
When telemetry is disabled the SDK constructs no transport, session, dispatcher, token provider or worker
|
|
427
427
|
threads, so there is no overhead.
|
|
@@ -457,7 +457,7 @@ phase is given the full budget afresh.
|
|
|
457
457
|
|
|
458
458
|
| Field | Default | Description |
|
|
459
459
|
| --- | --- | --- |
|
|
460
|
-
| `enabled` | `
|
|
460
|
+
| `enabled` | `True` | Master on/off switch for telemetry. |
|
|
461
461
|
| `endpoint_url` | `None` | Override for the telemetry ingest URL. When `None`, the URL is derived from the SDK environment. |
|
|
462
462
|
| `registry_discovery_document_url` | `None` | Override for the Registry OIDC discovery document URL used to obtain a telemetry access token. When `None`, derived from the environment. |
|
|
463
463
|
| `delivery_mode` | `TelemetryDeliveryMode.ASYNC` | `ASYNC` (background dispatch) or `SYNC` (inline delivery). |
|
|
@@ -602,8 +602,13 @@ poetry publish
|
|
|
602
602
|
|
|
603
603
|
## Release Notes
|
|
604
604
|
|
|
605
|
+
### 1.5.2 (Sep 30, 2026)
|
|
606
|
+
- **Telemetry re-enabled by default**: Telemetry is now enabled by default again after the concurrency and shutdown bugfixes below. Opt out with `TelemetryConfig(enabled=False)`.
|
|
607
|
+
- **Concurrent telemetry session isolation**: Fixed a race condition where overlapping SDK operations could share or corrupt telemetry session state.
|
|
608
|
+
- **Daemon thread shutdown with atexit handler**: Fixed an issue where daemon worker threads were not cleaned up when `close()` was not called explicitly, by registering an `atexit` handler for graceful shutdown.
|
|
609
|
+
|
|
605
610
|
### 1.5.1 (Sep 25, 2026)
|
|
606
|
-
- **Telemetry disabled by default**: Telemetry
|
|
611
|
+
- **Telemetry disabled by default**: Telemetry was temporarily disabled while a bugfix was in progress.
|
|
607
612
|
|
|
608
613
|
### 1.5.0 (Sep 22, 2026)
|
|
609
614
|
- **SDK Telemetry**: Added opt-out telemetry for PAR, Token, and UserInfo endpoint calls — see the [Telemetry](#telemetry) section for full details
|
|
@@ -398,7 +398,7 @@ with a unique `x-fapi-interaction-id` that was used for this request to the auth
|
|
|
398
398
|
The SDK emits non-sensitive telemetry about its PAR, Token and UserInfo endpoint interactions to the
|
|
399
399
|
centralized ConnectID telemetry service over mTLS. Telemetry is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
400
400
|
|
|
401
|
-
> **Note:** Telemetry is
|
|
401
|
+
> **Note:** Telemetry is **enabled by default**. To opt out, set `TelemetryConfig(enabled=False)` on your `CustomConfig`.
|
|
402
402
|
|
|
403
403
|
When telemetry is disabled the SDK constructs no transport, session, dispatcher, token provider or worker
|
|
404
404
|
threads, so there is no overhead.
|
|
@@ -434,7 +434,7 @@ phase is given the full budget afresh.
|
|
|
434
434
|
|
|
435
435
|
| Field | Default | Description |
|
|
436
436
|
| --- | --- | --- |
|
|
437
|
-
| `enabled` | `
|
|
437
|
+
| `enabled` | `True` | Master on/off switch for telemetry. |
|
|
438
438
|
| `endpoint_url` | `None` | Override for the telemetry ingest URL. When `None`, the URL is derived from the SDK environment. |
|
|
439
439
|
| `registry_discovery_document_url` | `None` | Override for the Registry OIDC discovery document URL used to obtain a telemetry access token. When `None`, derived from the environment. |
|
|
440
440
|
| `delivery_mode` | `TelemetryDeliveryMode.ASYNC` | `ASYNC` (background dispatch) or `SYNC` (inline delivery). |
|
|
@@ -579,8 +579,13 @@ poetry publish
|
|
|
579
579
|
|
|
580
580
|
## Release Notes
|
|
581
581
|
|
|
582
|
+
### 1.5.2 (Sep 30, 2026)
|
|
583
|
+
- **Telemetry re-enabled by default**: Telemetry is now enabled by default again after the concurrency and shutdown bugfixes below. Opt out with `TelemetryConfig(enabled=False)`.
|
|
584
|
+
- **Concurrent telemetry session isolation**: Fixed a race condition where overlapping SDK operations could share or corrupt telemetry session state.
|
|
585
|
+
- **Daemon thread shutdown with atexit handler**: Fixed an issue where daemon worker threads were not cleaned up when `close()` was not called explicitly, by registering an `atexit` handler for graceful shutdown.
|
|
586
|
+
|
|
582
587
|
### 1.5.1 (Sep 25, 2026)
|
|
583
|
-
- **Telemetry disabled by default**: Telemetry
|
|
588
|
+
- **Telemetry disabled by default**: Telemetry was temporarily disabled while a bugfix was in progress.
|
|
584
589
|
|
|
585
590
|
### 1.5.0 (Sep 22, 2026)
|
|
586
591
|
- **SDK Telemetry**: Added opt-out telemetry for PAR, Token, and UserInfo endpoint calls — see the [Telemetry](#telemetry) section for full details
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "rp_python_sdk"
|
|
3
|
-
version = "1.5.
|
|
3
|
+
version = "1.5.2"
|
|
4
4
|
description = "Python SDK for Relying Parties to enable simple integration with the Digital Identity ecosystem"
|
|
5
5
|
authors = ["Erik Pragt <erik.pragt@connectid.com.au>"]
|
|
6
6
|
license = "Proprietary"
|
|
@@ -25,13 +25,24 @@ def _create_telemetry_sender(config: SdkConfig) -> TelemetrySender:
|
|
|
25
25
|
logger.info("Telemetry is disabled; no telemetry events will be sent.")
|
|
26
26
|
return NoopTelemetrySender.instance()
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
transport: Optional[TelemetryTransport] = None
|
|
29
|
+
try:
|
|
30
|
+
transport = TelemetryTransport.create(config)
|
|
31
|
+
if telemetry.delivery_mode is TelemetryDeliveryMode.SYNC:
|
|
32
|
+
sender: TelemetrySender = SyncTelemetrySender(
|
|
33
|
+
transport, telemetry.sync_send_budget_seconds)
|
|
34
|
+
else:
|
|
35
|
+
dispatcher = TelemetryDispatcher(
|
|
36
|
+
telemetry.queue_capacity, telemetry.max_concurrent_sends)
|
|
37
|
+
sender = AsyncTelemetrySender(transport, dispatcher)
|
|
38
|
+
except Exception as exception:
|
|
39
|
+
if transport is not None:
|
|
40
|
+
transport.close()
|
|
41
|
+
logger.warning(f"Failed to initialise telemetry; telemetry is disabled: {exception}")
|
|
42
|
+
return NoopTelemetrySender.instance()
|
|
32
43
|
|
|
33
|
-
|
|
34
|
-
return
|
|
44
|
+
logger.info("Telemetry is enabled; telemetry events will be sent to the telemetry service.")
|
|
45
|
+
return sender
|
|
35
46
|
|
|
36
47
|
|
|
37
48
|
def _close_telemetry_sender(sender: TelemetrySender) -> None:
|
|
@@ -10,7 +10,7 @@ class TelemetryDeliveryMode(str, Enum):
|
|
|
10
10
|
|
|
11
11
|
@dataclass
|
|
12
12
|
class TelemetryConfig:
|
|
13
|
-
enabled: bool =
|
|
13
|
+
enabled: bool = True
|
|
14
14
|
endpoint_url: Optional[str] = None
|
|
15
15
|
registry_discovery_document_url: Optional[str] = None
|
|
16
16
|
delivery_mode: TelemetryDeliveryMode = TelemetryDeliveryMode.ASYNC
|
|
@@ -1,14 +1,41 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import queue
|
|
2
3
|
import threading
|
|
3
4
|
import time
|
|
5
|
+
import weakref
|
|
4
6
|
from typing import Callable, List
|
|
5
7
|
|
|
6
8
|
from rp_python_sdk.setup_logger import logger
|
|
7
9
|
|
|
8
10
|
DEFAULT_GRACE_PERIOD_SECONDS = 2.0
|
|
11
|
+
DEFAULT_EXIT_GRACE_PERIOD_SECONDS = 12.0
|
|
9
12
|
DEFAULT_IDLE_TIMEOUT_SECONDS = 30.0
|
|
10
13
|
POLL_INTERVAL_SECONDS = 0.1
|
|
11
14
|
|
|
15
|
+
# Weak references to all live dispatchers. The atexit handler iterates these and
|
|
16
|
+
# calls close() on any that are still alive, ensuring daemon worker threads are
|
|
17
|
+
# joined before the interpreter tears down — preventing the
|
|
18
|
+
# 'Fatal Python error: Aborted' on Python 3.12/Linux when threads are in C-level SSL code.
|
|
19
|
+
_live_dispatchers: weakref.WeakSet["TelemetryDispatcher"] = weakref.WeakSet()
|
|
20
|
+
_atexit_registered = False
|
|
21
|
+
_atexit_lock = threading.Lock()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _shutdown_all_dispatchers() -> None:
|
|
25
|
+
for dispatcher in list(_live_dispatchers):
|
|
26
|
+
try:
|
|
27
|
+
dispatcher.close(grace_period_seconds=DEFAULT_EXIT_GRACE_PERIOD_SECONDS)
|
|
28
|
+
except Exception as exception:
|
|
29
|
+
logger.debug(f"Ignoring error while closing telemetry dispatcher at exit: {exception}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _register_atexit_hook() -> None:
|
|
33
|
+
global _atexit_registered
|
|
34
|
+
with _atexit_lock:
|
|
35
|
+
if not _atexit_registered:
|
|
36
|
+
atexit.register(_shutdown_all_dispatchers)
|
|
37
|
+
_atexit_registered = True
|
|
38
|
+
|
|
12
39
|
|
|
13
40
|
class TelemetryDispatcher:
|
|
14
41
|
|
|
@@ -32,6 +59,9 @@ class TelemetryDispatcher:
|
|
|
32
59
|
# Monotonic so a replacement worker never reuses a retired worker's thread name.
|
|
33
60
|
self._next_worker_number = 0
|
|
34
61
|
|
|
62
|
+
_live_dispatchers.add(self)
|
|
63
|
+
_register_atexit_hook()
|
|
64
|
+
|
|
35
65
|
def enqueue(self, task: Callable[[], None]) -> bool:
|
|
36
66
|
"""Queues a task for background execution, returning False when it was dropped.
|
|
37
67
|
|
|
@@ -54,11 +84,15 @@ class TelemetryDispatcher:
|
|
|
54
84
|
|
|
55
85
|
def close(self, grace_period_seconds: float = DEFAULT_GRACE_PERIOD_SECONDS) -> None:
|
|
56
86
|
with self._lock:
|
|
87
|
+
if self._shutdown.is_set():
|
|
88
|
+
return
|
|
57
89
|
self._shutdown.set()
|
|
58
90
|
self._discard_pending()
|
|
59
91
|
workers = list(self._workers)
|
|
60
92
|
self._workers.clear()
|
|
61
93
|
|
|
94
|
+
_live_dispatchers.discard(self)
|
|
95
|
+
|
|
62
96
|
deadline = time.monotonic() + grace_period_seconds
|
|
63
97
|
for worker in workers:
|
|
64
98
|
remaining = max(0.0, deadline - time.monotonic())
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import queue
|
|
2
|
+
import threading
|
|
3
|
+
from typing import Iterable, Optional
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from rp_python_sdk.setup_logger import logger
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TelemetrySessionPool:
|
|
11
|
+
"""Hands each telemetry send exclusive ownership of one HTTP session.
|
|
12
|
+
|
|
13
|
+
``requests.Session`` contains mutable adapter and connection-pool state. A session therefore
|
|
14
|
+
stays leased to one worker for the complete token-and-event exchange. Shutdown closes idle
|
|
15
|
+
sessions immediately, while a leased session is closed by the worker that returns it. This
|
|
16
|
+
keeps shutdown bounded without closing TLS resources underneath an in-flight request.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, sessions: Iterable[requests.Session]) -> None:
|
|
20
|
+
available_sessions = list(sessions)
|
|
21
|
+
if not available_sessions:
|
|
22
|
+
raise ValueError("At least one telemetry session is required")
|
|
23
|
+
|
|
24
|
+
self._available: queue.LifoQueue[requests.Session] = queue.LifoQueue(
|
|
25
|
+
maxsize=len(available_sessions)
|
|
26
|
+
)
|
|
27
|
+
for session in available_sessions:
|
|
28
|
+
self._available.put_nowait(session)
|
|
29
|
+
|
|
30
|
+
self._lock = threading.Lock()
|
|
31
|
+
self._closed = False
|
|
32
|
+
|
|
33
|
+
def acquire(self) -> Optional[requests.Session]:
|
|
34
|
+
"""Returns an exclusively leased session, or None without blocking."""
|
|
35
|
+
with self._lock:
|
|
36
|
+
if self._closed:
|
|
37
|
+
return None
|
|
38
|
+
try:
|
|
39
|
+
return self._available.get_nowait()
|
|
40
|
+
except queue.Empty:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
def release(self, session: requests.Session) -> None:
|
|
44
|
+
"""Returns a lease, closing it instead when pool shutdown has begun."""
|
|
45
|
+
close_session = False
|
|
46
|
+
with self._lock:
|
|
47
|
+
if self._closed:
|
|
48
|
+
close_session = True
|
|
49
|
+
else:
|
|
50
|
+
try:
|
|
51
|
+
self._available.put_nowait(session)
|
|
52
|
+
except queue.Full:
|
|
53
|
+
# A duplicate release is a lifecycle bug, but telemetry cleanup must not
|
|
54
|
+
# surface it to application code. Close the redundant resource instead.
|
|
55
|
+
close_session = True
|
|
56
|
+
|
|
57
|
+
if close_session:
|
|
58
|
+
self._close_session(session)
|
|
59
|
+
|
|
60
|
+
def close(self) -> None:
|
|
61
|
+
"""Prevents new leases and closes idle resources without waiting for active sends."""
|
|
62
|
+
idle_sessions: list[requests.Session] = []
|
|
63
|
+
with self._lock:
|
|
64
|
+
if self._closed:
|
|
65
|
+
return
|
|
66
|
+
self._closed = True
|
|
67
|
+
while True:
|
|
68
|
+
try:
|
|
69
|
+
idle_sessions.append(self._available.get_nowait())
|
|
70
|
+
except queue.Empty:
|
|
71
|
+
break
|
|
72
|
+
|
|
73
|
+
for session in idle_sessions:
|
|
74
|
+
self._close_session(session)
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def _close_session(session: requests.Session) -> None:
|
|
78
|
+
try:
|
|
79
|
+
session.close()
|
|
80
|
+
except Exception as exception:
|
|
81
|
+
logger.debug(f"Ignoring error while closing telemetry HTTP session: {exception}")
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_token_provider.py
RENAMED
|
@@ -27,22 +27,24 @@ class TelemetryTokenProvider:
|
|
|
27
27
|
self._expires_at: float = 0.0
|
|
28
28
|
self._discovery_document: Optional[dict[str, Any]] = None
|
|
29
29
|
|
|
30
|
-
def get_access_token(self, timeout: RequestsTimeout
|
|
30
|
+
def get_access_token(self, timeout: RequestsTimeout,
|
|
31
|
+
session: Optional[requests.Session] = None) -> Optional[str]:
|
|
31
32
|
if self._is_token_valid():
|
|
32
33
|
return self._access_token
|
|
33
34
|
|
|
34
35
|
with self._lock:
|
|
35
36
|
if self._is_token_valid():
|
|
36
37
|
return self._access_token
|
|
37
|
-
|
|
38
|
+
selected_session = session if session is not None else self._session
|
|
39
|
+
return self._refresh_token(timeout, selected_session)
|
|
38
40
|
|
|
39
41
|
def _is_token_valid(self) -> bool:
|
|
40
42
|
return (self._access_token is not None
|
|
41
43
|
and time.time() + self.TOKEN_REFRESH_BUFFER_SECONDS < self._expires_at)
|
|
42
44
|
|
|
43
|
-
def _refresh_token(self, timeout: RequestsTimeout) -> Optional[str]:
|
|
45
|
+
def _refresh_token(self, timeout: RequestsTimeout, session: requests.Session) -> Optional[str]:
|
|
44
46
|
try:
|
|
45
|
-
discovery_document = self._get_discovery_document(timeout)
|
|
47
|
+
discovery_document = self._get_discovery_document(timeout, session)
|
|
46
48
|
token_endpoint = self._resolve_token_endpoint(discovery_document)
|
|
47
49
|
audience = discovery_document.get("issuer") or token_endpoint
|
|
48
50
|
|
|
@@ -57,7 +59,7 @@ class TelemetryTokenProvider:
|
|
|
57
59
|
"client_assertion_type": _CLIENT_ASSERTION_TYPE,
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
response =
|
|
62
|
+
response = session.post(token_endpoint, data=data, timeout=timeout)
|
|
61
63
|
response.raise_for_status()
|
|
62
64
|
token_response = response.json()
|
|
63
65
|
|
|
@@ -73,11 +75,12 @@ class TelemetryTokenProvider:
|
|
|
73
75
|
self._expires_at = 0.0
|
|
74
76
|
return None
|
|
75
77
|
|
|
76
|
-
def _get_discovery_document(self, timeout: RequestsTimeout
|
|
78
|
+
def _get_discovery_document(self, timeout: RequestsTimeout,
|
|
79
|
+
session: requests.Session) -> dict[str, Any]:
|
|
77
80
|
if self._discovery_document is not None:
|
|
78
81
|
return self._discovery_document
|
|
79
82
|
|
|
80
|
-
response =
|
|
83
|
+
response = session.get(self._registry_discovery_url, timeout=timeout)
|
|
81
84
|
response.raise_for_status()
|
|
82
85
|
discovery_document: dict[str, Any] = response.json()
|
|
83
86
|
|
|
@@ -89,4 +92,3 @@ class TelemetryTokenProvider:
|
|
|
89
92
|
mtls_aliases = discovery_document.get("mtls_endpoint_aliases") or {}
|
|
90
93
|
token_endpoint = mtls_aliases.get("token_endpoint") or discovery_document["token_endpoint"]
|
|
91
94
|
return str(token_endpoint)
|
|
92
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from typing import Any, Optional
|
|
2
|
+
from typing import Any, Optional, Sequence
|
|
3
3
|
|
|
4
4
|
import requests
|
|
5
5
|
|
|
@@ -7,6 +7,7 @@ from rp_python_sdk.sdk_config import SdkConfig
|
|
|
7
7
|
from rp_python_sdk.setup_logger import logger
|
|
8
8
|
from rp_python_sdk.telemetry.telemetry_event import TelemetryEvent
|
|
9
9
|
from rp_python_sdk.telemetry.telemetry_session import create_telemetry_session
|
|
10
|
+
from rp_python_sdk.telemetry.telemetry_session_pool import TelemetrySessionPool
|
|
10
11
|
from rp_python_sdk.telemetry.telemetry_token_provider import (
|
|
11
12
|
TelemetryTokenProvider,
|
|
12
13
|
RequestsTimeout,
|
|
@@ -19,19 +20,34 @@ _JSON_MEDIA_TYPE = "application/json"
|
|
|
19
20
|
|
|
20
21
|
class TelemetryTransport:
|
|
21
22
|
def __init__(self, session: requests.Session, token_provider: TelemetryTokenProvider,
|
|
22
|
-
telemetry_endpoint_url: str
|
|
23
|
-
|
|
23
|
+
telemetry_endpoint_url: str,
|
|
24
|
+
additional_sessions: Sequence[requests.Session] = ()) -> None:
|
|
25
|
+
self._sessions = TelemetrySessionPool([session, *additional_sessions])
|
|
24
26
|
self._token_provider = token_provider
|
|
25
27
|
self._telemetry_endpoint_url = telemetry_endpoint_url
|
|
26
28
|
|
|
27
29
|
@classmethod
|
|
28
30
|
def create(cls, config: SdkConfig) -> "TelemetryTransport":
|
|
29
31
|
telemetry_urls = resolve_telemetry_urls(config)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
session_count = config.custom_config.telemetry_config.max_concurrent_sends
|
|
33
|
+
sessions: list[requests.Session] = []
|
|
34
|
+
try:
|
|
35
|
+
# A session is never shared between workers. Each session needs only one connection
|
|
36
|
+
# slot because the dispatcher already bounds the number of concurrent sends.
|
|
37
|
+
for _ in range(session_count):
|
|
38
|
+
sessions.append(create_telemetry_session(config, pool_size=1))
|
|
39
|
+
token_provider = TelemetryTokenProvider(
|
|
40
|
+
sessions[0], config, telemetry_urls.registry_discovery_document_url)
|
|
41
|
+
return cls(sessions[0], token_provider, telemetry_urls.telemetry_endpoint_url,
|
|
42
|
+
additional_sessions=sessions[1:])
|
|
43
|
+
except Exception:
|
|
44
|
+
for session in sessions:
|
|
45
|
+
try:
|
|
46
|
+
session.close()
|
|
47
|
+
except Exception as close_error:
|
|
48
|
+
logger.debug(f"Ignoring error while cleaning up telemetry HTTP session: "
|
|
49
|
+
f"{close_error}")
|
|
50
|
+
raise
|
|
35
51
|
|
|
36
52
|
@property
|
|
37
53
|
def token_provider(self) -> TelemetryTokenProvider:
|
|
@@ -39,13 +55,20 @@ class TelemetryTransport:
|
|
|
39
55
|
|
|
40
56
|
def send(self, event: TelemetryEvent, timeout: RequestsTimeout,
|
|
41
57
|
token: Optional[str] = None) -> None:
|
|
58
|
+
session = self._sessions.acquire()
|
|
59
|
+
if session is None:
|
|
60
|
+
logger.debug(f"Skipping telemetry event type {event.type} "
|
|
61
|
+
f"(x-fapi-interaction-id: {event.x_fapi_interaction_id}): "
|
|
62
|
+
f"no exclusive HTTP session is available.")
|
|
63
|
+
return
|
|
64
|
+
|
|
42
65
|
try:
|
|
43
66
|
logger.info(f"Dispatching telemetry event type {event.type} "
|
|
44
67
|
f"(x-fapi-interaction-id: {event.x_fapi_interaction_id}) "
|
|
45
68
|
f"to {self._telemetry_endpoint_url}")
|
|
46
69
|
|
|
47
70
|
if token is None:
|
|
48
|
-
token = self._token_provider.get_access_token(timeout)
|
|
71
|
+
token = self._token_provider.get_access_token(timeout, session)
|
|
49
72
|
if token is None:
|
|
50
73
|
logger.warning(f"Skipping telemetry event type {event.type} "
|
|
51
74
|
f"(x-fapi-interaction-id: {event.x_fapi_interaction_id}): "
|
|
@@ -59,9 +82,9 @@ class TelemetryTransport:
|
|
|
59
82
|
_FAPI_INTERACTION_ID_HEADER: event.x_fapi_interaction_id,
|
|
60
83
|
}
|
|
61
84
|
|
|
62
|
-
response =
|
|
63
|
-
|
|
64
|
-
|
|
85
|
+
response = session.post(self._telemetry_endpoint_url, data=body,
|
|
86
|
+
headers=headers,
|
|
87
|
+
timeout=timeout)
|
|
65
88
|
|
|
66
89
|
if not response.ok:
|
|
67
90
|
logger.warning(f"Telemetry send for event type {event.type} "
|
|
@@ -74,6 +97,8 @@ class TelemetryTransport:
|
|
|
74
97
|
except Exception as exception:
|
|
75
98
|
logger.warning(f"Failed to send telemetry event type {event.type} "
|
|
76
99
|
f"(x-fapi-interaction-id: {event.x_fapi_interaction_id}): {exception}")
|
|
100
|
+
finally:
|
|
101
|
+
self._sessions.release(session)
|
|
77
102
|
|
|
78
103
|
def close(self) -> None:
|
|
79
|
-
self.
|
|
104
|
+
self._sessions.close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/pushed_authorisation_request.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/client_assertion.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/relying_party_client_sdk_exception.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/async_telemetry_sender.py
RENAMED
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/sync_telemetry_sender.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_event_factory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.1 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_url_resolver.py
RENAMED
|
File without changes
|