clarity-api-sdk-python 0.1.9__tar.gz → 0.2.12__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.
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/PKG-INFO +26 -1
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/README.md +25 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/pyproject.toml +1 -1
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/clarity_api_sdk_python.egg-info/PKG-INFO +26 -1
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/clarity_api_sdk_python.egg-info/SOURCES.txt +2 -0
- clarity_api_sdk_python-0.2.12/src/cti/api/__init__.py +5 -0
- clarity_api_sdk_python-0.2.12/src/cti/api/async_client.py +127 -0
- clarity_api_sdk_python-0.2.12/src/cti/api/session.py +59 -0
- clarity_api_sdk_python-0.1.9/src/cti/api/__init__.py +0 -3
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/setup.cfg +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/clarity_api_sdk_python.egg-info/dependency_links.txt +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/clarity_api_sdk_python.egg-info/requires.txt +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/clarity_api_sdk_python.egg-info/top_level.txt +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/cti/__init__.py +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/cti/api/client.py +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/cti/logger/__init__.py +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/cti/logger/logger.py +0 -0
- {clarity_api_sdk_python-0.1.9 → clarity_api_sdk_python-0.2.12}/src/cti/main.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clarity-api-sdk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Summary: A Python SDK to connect to the CTI Clarity API server.
|
|
5
5
|
Author-email: "Chesapeake Technology Inc." <support@chesapeaketech.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/chesapeake-tech/clarity-api-sdk-python
|
|
@@ -77,3 +77,28 @@ logger_a.warning("This is a new warning message from logger_a.")
|
|
|
77
77
|
logger_b.info("This info message from logger_b should NOT be visible.")
|
|
78
78
|
logger_b.warning("This is warning message from logger_b")
|
|
79
79
|
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### Singleton async client
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import asyncio
|
|
87
|
+
from cti.api.session import initialize_async_client, close_async_client
|
|
88
|
+
|
|
89
|
+
async def main():
|
|
90
|
+
await initialize_async_client()
|
|
91
|
+
# ... your application logic ...
|
|
92
|
+
await close_async_client()
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
|
|
97
|
+
# In other modules
|
|
98
|
+
from cti.api.session import async_client
|
|
99
|
+
|
|
100
|
+
async def fetch_data():
|
|
101
|
+
if async_client:
|
|
102
|
+
response = await async_client.get(...)
|
|
103
|
+
return response.json()
|
|
104
|
+
```
|
|
@@ -55,3 +55,28 @@ logger_a.warning("This is a new warning message from logger_a.")
|
|
|
55
55
|
logger_b.info("This info message from logger_b should NOT be visible.")
|
|
56
56
|
logger_b.warning("This is warning message from logger_b")
|
|
57
57
|
```
|
|
58
|
+
|
|
59
|
+
## API
|
|
60
|
+
|
|
61
|
+
### Singleton async client
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import asyncio
|
|
65
|
+
from cti.api.session import initialize_async_client, close_async_client
|
|
66
|
+
|
|
67
|
+
async def main():
|
|
68
|
+
await initialize_async_client()
|
|
69
|
+
# ... your application logic ...
|
|
70
|
+
await close_async_client()
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
asyncio.run(main())
|
|
74
|
+
|
|
75
|
+
# In other modules
|
|
76
|
+
from cti.api.session import async_client
|
|
77
|
+
|
|
78
|
+
async def fetch_data():
|
|
79
|
+
if async_client:
|
|
80
|
+
response = await async_client.get(...)
|
|
81
|
+
return response.json()
|
|
82
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: clarity-api-sdk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.12
|
|
4
4
|
Summary: A Python SDK to connect to the CTI Clarity API server.
|
|
5
5
|
Author-email: "Chesapeake Technology Inc." <support@chesapeaketech.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/chesapeake-tech/clarity-api-sdk-python
|
|
@@ -77,3 +77,28 @@ logger_a.warning("This is a new warning message from logger_a.")
|
|
|
77
77
|
logger_b.info("This info message from logger_b should NOT be visible.")
|
|
78
78
|
logger_b.warning("This is warning message from logger_b")
|
|
79
79
|
```
|
|
80
|
+
|
|
81
|
+
## API
|
|
82
|
+
|
|
83
|
+
### Singleton async client
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
import asyncio
|
|
87
|
+
from cti.api.session import initialize_async_client, close_async_client
|
|
88
|
+
|
|
89
|
+
async def main():
|
|
90
|
+
await initialize_async_client()
|
|
91
|
+
# ... your application logic ...
|
|
92
|
+
await close_async_client()
|
|
93
|
+
|
|
94
|
+
if __name__ == "__main__":
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
|
|
97
|
+
# In other modules
|
|
98
|
+
from cti.api.session import async_client
|
|
99
|
+
|
|
100
|
+
async def fetch_data():
|
|
101
|
+
if async_client:
|
|
102
|
+
response = await async_client.get(...)
|
|
103
|
+
return response.json()
|
|
104
|
+
```
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Async client for clarity API"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
|
|
6
|
+
from httpx import AsyncClient, HTTPStatusError, RequestError, Response, URL
|
|
7
|
+
from httpx_auth import OAuth2ClientCredentials, OAuth2, TokenMemoryCache
|
|
8
|
+
from httpx_retries import Retry, RetryTransport
|
|
9
|
+
|
|
10
|
+
from cti.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
OAuth2.token_cache = TokenMemoryCache()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ClarityApiAsyncClient(AsyncClient):
|
|
17
|
+
"""Async client for Clarity API configured with OAuth2 authentication, retry mechanism
|
|
18
|
+
and other defaults to connect to the clarity server.
|
|
19
|
+
|
|
20
|
+
Reuse this client instance for multiple requests for faster performance.
|
|
21
|
+
|
|
22
|
+
The authorization token is cached and shared between each client instance to minimize
|
|
23
|
+
calls to the https://auth.sonarwiz.io/ keycloak server.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
|
|
28
|
+
# credentials for Clarity API
|
|
29
|
+
cti_credentials = OAuth2ClientCredentials(
|
|
30
|
+
token_url=(
|
|
31
|
+
f'{os.environ.get("KEYCLOAK_SERVER_URL", "missing KEYCLOAK_SERVER_URL")}/realms/'
|
|
32
|
+
f'{os.environ.get("KEYCLOAK_REALM", "missing KEYCLOAK_REALM")}'
|
|
33
|
+
"/protocol/openid-connect/token"
|
|
34
|
+
),
|
|
35
|
+
client_id=os.environ.get(
|
|
36
|
+
"KEYCLOAK_CLIENT_ID", "missing KEYCLOAK_CLIENT_ID"
|
|
37
|
+
),
|
|
38
|
+
client_secret=os.environ.get(
|
|
39
|
+
"KEYCLOAK_CLIENT_SECRET", "missing KEYCLOAK_CLIENT_SECRET"
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# retry mechanism for API requests
|
|
44
|
+
retry = Retry(total=12, backoff_factor=0.5)
|
|
45
|
+
transport = RetryTransport(retry=retry)
|
|
46
|
+
|
|
47
|
+
super().__init__(
|
|
48
|
+
base_url=os.environ.get("CLARITY_API_URL", "missing Clarity_API_URL"),
|
|
49
|
+
auth=cti_credentials,
|
|
50
|
+
timeout=60,
|
|
51
|
+
transport=transport,
|
|
52
|
+
http2=True,
|
|
53
|
+
headers={"Accept": "application/json"},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
async def request(self, method: str, url: URL | str, **kwargs) -> Response:
|
|
57
|
+
"""Make an async request to the Clarity API and handle exceptions. Using
|
|
58
|
+
this allows requests to be made concurrently and can provide significantly
|
|
59
|
+
improved performance. Use this to make multiple concurrent requests.
|
|
60
|
+
|
|
61
|
+
The exceptions are caught and logged, and then re-raised.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
method: HTTP method (GET, POST, PUT, DELETE, etc.)
|
|
65
|
+
url: relative URL for the request, eg: "/api/v1/projects/12345"
|
|
66
|
+
kwargs: additional keyword arguments to be passed to the request
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
httpx.Response: Response from the API
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
RequestError: If there was an issue with the request
|
|
73
|
+
HTTPStatusError: If the response status code is not in the 2xx range
|
|
74
|
+
Exception: For any other uncaught exception
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
request_id = str(uuid.uuid4())
|
|
78
|
+
if "headers" not in kwargs or kwargs["headers"] is None:
|
|
79
|
+
kwargs["headers"] = {}
|
|
80
|
+
# append x-request-id header to the kwargs "headers"
|
|
81
|
+
kwargs["headers"].update({"x-request-id": request_id})
|
|
82
|
+
logger.info(
|
|
83
|
+
"request",
|
|
84
|
+
extra={"url": url, "request_id": request_id},
|
|
85
|
+
)
|
|
86
|
+
# make the actual request and return the response
|
|
87
|
+
response = await super().request(method, url, **kwargs)
|
|
88
|
+
logger.info(
|
|
89
|
+
"response",
|
|
90
|
+
extra={
|
|
91
|
+
"request_id": request_id,
|
|
92
|
+
"response": {"status_code": response.status_code},
|
|
93
|
+
},
|
|
94
|
+
)
|
|
95
|
+
return response
|
|
96
|
+
except HTTPStatusError as e:
|
|
97
|
+
logger.error(
|
|
98
|
+
"http",
|
|
99
|
+
extra={
|
|
100
|
+
"request": {
|
|
101
|
+
"method": e.request.method,
|
|
102
|
+
"url": str(e.request.url),
|
|
103
|
+
"headers": dict(e.request.headers),
|
|
104
|
+
},
|
|
105
|
+
"error": {
|
|
106
|
+
"message": str(e.response.content),
|
|
107
|
+
"status_code": e.response.status_code,
|
|
108
|
+
"headers": dict(e.response.headers),
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
)
|
|
112
|
+
raise e
|
|
113
|
+
except RequestError as e:
|
|
114
|
+
logger.error(
|
|
115
|
+
"request",
|
|
116
|
+
extra={
|
|
117
|
+
"request": {
|
|
118
|
+
"method": e.request.method,
|
|
119
|
+
"url": str(e.request.url),
|
|
120
|
+
"headers": dict(e.request.headers),
|
|
121
|
+
},
|
|
122
|
+
"error": {
|
|
123
|
+
"message": str(e),
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
)
|
|
127
|
+
raise e
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Provides a shared, singleton instance of the ClarityApiAsyncClient.
|
|
2
|
+
|
|
3
|
+
This module allows for a single, reusable async client to be initialized
|
|
4
|
+
and accessed throughout the application, promoting connection reuse and
|
|
5
|
+
efficiency. The client should be initialized at application startup
|
|
6
|
+
and closed gracefully on shutdown.
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
# In your main application entry point
|
|
10
|
+
import asyncio
|
|
11
|
+
from cti.api.session import initialize_async_client, close_async_client
|
|
12
|
+
|
|
13
|
+
async def main():
|
|
14
|
+
await initialize_async_client()
|
|
15
|
+
# ... your application logic ...
|
|
16
|
+
await close_async_client()
|
|
17
|
+
|
|
18
|
+
if __name__ == "__main__":
|
|
19
|
+
asyncio.run(main())
|
|
20
|
+
|
|
21
|
+
# In other modules
|
|
22
|
+
from cti.api.session import async_client
|
|
23
|
+
|
|
24
|
+
async def fetch_data():
|
|
25
|
+
if async_client:
|
|
26
|
+
response = await async_client.get(...)
|
|
27
|
+
return response.json()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# pylint: disable=global-statement, unnecessary-dunder-call
|
|
31
|
+
|
|
32
|
+
from cti.api.async_client import ClarityApiAsyncClient
|
|
33
|
+
|
|
34
|
+
# The singleton instance, initially None.
|
|
35
|
+
async_client: ClarityApiAsyncClient | None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def initialize_async_client() -> None:
|
|
39
|
+
"""Initializes the shared ClarityApiAsyncClient instance.
|
|
40
|
+
|
|
41
|
+
If the client is already initialized, this function does nothing.
|
|
42
|
+
This function should be awaited at application startup.
|
|
43
|
+
"""
|
|
44
|
+
global async_client
|
|
45
|
+
if async_client is None:
|
|
46
|
+
async_client = ClarityApiAsyncClient()
|
|
47
|
+
await async_client.__aenter__()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
async def close_async_client() -> None:
|
|
51
|
+
"""Closes the shared ClarityApiAsyncClient instance.
|
|
52
|
+
|
|
53
|
+
This function should be awaited at application shutdown to ensure
|
|
54
|
+
the underlying connection pool is closed gracefully.
|
|
55
|
+
"""
|
|
56
|
+
global async_client
|
|
57
|
+
if async_client:
|
|
58
|
+
await async_client.__aexit__(None, None, None)
|
|
59
|
+
async_client = None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|