rp_python_sdk 1.5.0__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.0 → rp_python_sdk-1.5.2}/PKG-INFO +11 -21
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/README.md +10 -20
- {rp_python_sdk-1.5.0 → 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.0 → rp_python_sdk-1.5.2}/rp_python_sdk/relying_party_client_sdk.py +17 -6
- {rp_python_sdk-1.5.0 → 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.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_token_provider.py +10 -8
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_transport.py +38 -13
- rp_python_sdk-1.5.0/rp_python_sdk/endpoints/util/version_info.py +0 -2
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/__init__.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/cache/__init__.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/cache/http_cache.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/get_participants.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/pushed_authorisation_request.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/retrieve_tokens.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/user_info.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/client_assertion.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/fapi.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/mapping.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/system_info.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/endpoints/util/user_agent.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/filters/participant_filters.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/model.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/relying_party_client_sdk_exception.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/sdk_config.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/setup_logger.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/__init__.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/async_telemetry_sender.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/sync_telemetry_sender.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_config.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_context.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_event.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_event_factory.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_sender.py +0 -0
- {rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_session.py +0 -0
- {rp_python_sdk-1.5.0 → 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
|
|
@@ -419,27 +419,9 @@ with a unique `x-fapi-interaction-id` that was used for this request to the auth
|
|
|
419
419
|
## Telemetry
|
|
420
420
|
|
|
421
421
|
The SDK emits non-sensitive telemetry about its PAR, Token and UserInfo endpoint interactions to the
|
|
422
|
-
centralized ConnectID telemetry service over mTLS. Telemetry is
|
|
423
|
-
configuration. It is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
422
|
+
centralized ConnectID telemetry service over mTLS. Telemetry is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
424
423
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
To disable telemetry, set `enabled=False` on the `TelemetryConfig`:
|
|
428
|
-
|
|
429
|
-
```python
|
|
430
|
-
from rp_python_sdk.relying_party_client_sdk import RelyingPartyClientSdk
|
|
431
|
-
from rp_python_sdk.sdk_config import SdkConfig, CustomConfig
|
|
432
|
-
from rp_python_sdk.telemetry.telemetry_config import TelemetryConfig
|
|
433
|
-
|
|
434
|
-
client = RelyingPartyClientSdk(
|
|
435
|
-
config=SdkConfig(
|
|
436
|
-
# ... other SdkConfig fields ...
|
|
437
|
-
custom_config=CustomConfig(
|
|
438
|
-
telemetry_config=TelemetryConfig(enabled=False)
|
|
439
|
-
)
|
|
440
|
-
),
|
|
441
|
-
)
|
|
442
|
-
```
|
|
424
|
+
> **Note:** Telemetry is **enabled by default**. To opt out, set `TelemetryConfig(enabled=False)` on your `CustomConfig`.
|
|
443
425
|
|
|
444
426
|
When telemetry is disabled the SDK constructs no transport, session, dispatcher, token provider or worker
|
|
445
427
|
threads, so there is no overhead.
|
|
@@ -620,6 +602,14 @@ poetry publish
|
|
|
620
602
|
|
|
621
603
|
## Release Notes
|
|
622
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
|
+
|
|
610
|
+
### 1.5.1 (Sep 25, 2026)
|
|
611
|
+
- **Telemetry disabled by default**: Telemetry was temporarily disabled while a bugfix was in progress.
|
|
612
|
+
|
|
623
613
|
### 1.5.0 (Sep 22, 2026)
|
|
624
614
|
- **SDK Telemetry**: Added opt-out telemetry for PAR, Token, and UserInfo endpoint calls — see the [Telemetry](#telemetry) section for full details
|
|
625
615
|
- Supports ASYNC (default) and SYNC delivery modes
|
|
@@ -396,27 +396,9 @@ with a unique `x-fapi-interaction-id` that was used for this request to the auth
|
|
|
396
396
|
## Telemetry
|
|
397
397
|
|
|
398
398
|
The SDK emits non-sensitive telemetry about its PAR, Token and UserInfo endpoint interactions to the
|
|
399
|
-
centralized ConnectID telemetry service over mTLS. Telemetry is
|
|
400
|
-
configuration. It is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
399
|
+
centralized ConnectID telemetry service over mTLS. Telemetry is controlled through a `TelemetryConfig` supplied on `CustomConfig`.
|
|
401
400
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
To disable telemetry, set `enabled=False` on the `TelemetryConfig`:
|
|
405
|
-
|
|
406
|
-
```python
|
|
407
|
-
from rp_python_sdk.relying_party_client_sdk import RelyingPartyClientSdk
|
|
408
|
-
from rp_python_sdk.sdk_config import SdkConfig, CustomConfig
|
|
409
|
-
from rp_python_sdk.telemetry.telemetry_config import TelemetryConfig
|
|
410
|
-
|
|
411
|
-
client = RelyingPartyClientSdk(
|
|
412
|
-
config=SdkConfig(
|
|
413
|
-
# ... other SdkConfig fields ...
|
|
414
|
-
custom_config=CustomConfig(
|
|
415
|
-
telemetry_config=TelemetryConfig(enabled=False)
|
|
416
|
-
)
|
|
417
|
-
),
|
|
418
|
-
)
|
|
419
|
-
```
|
|
401
|
+
> **Note:** Telemetry is **enabled by default**. To opt out, set `TelemetryConfig(enabled=False)` on your `CustomConfig`.
|
|
420
402
|
|
|
421
403
|
When telemetry is disabled the SDK constructs no transport, session, dispatcher, token provider or worker
|
|
422
404
|
threads, so there is no overhead.
|
|
@@ -597,6 +579,14 @@ poetry publish
|
|
|
597
579
|
|
|
598
580
|
## Release Notes
|
|
599
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
|
+
|
|
587
|
+
### 1.5.1 (Sep 25, 2026)
|
|
588
|
+
- **Telemetry disabled by default**: Telemetry was temporarily disabled while a bugfix was in progress.
|
|
589
|
+
|
|
600
590
|
### 1.5.0 (Sep 22, 2026)
|
|
601
591
|
- **SDK Telemetry**: Added opt-out telemetry for PAR, Token, and UserInfo endpoint calls — see the [Telemetry](#telemetry) section for full details
|
|
602
592
|
- Supports ASYNC (default) and SYNC delivery modes
|
|
@@ -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:
|
|
@@ -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.0 → 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.0 → 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.0 → 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.0 → 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.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/async_telemetry_sender.py
RENAMED
|
File without changes
|
{rp_python_sdk-1.5.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/sync_telemetry_sender.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rp_python_sdk-1.5.0 → 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.0 → rp_python_sdk-1.5.2}/rp_python_sdk/telemetry/telemetry_url_resolver.py
RENAMED
|
File without changes
|