universal-mcp 0.1.23rc2__py3-none-any.whl → 0.1.24rc2__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.
- universal_mcp/analytics.py +43 -11
- universal_mcp/applications/application.py +186 -132
- universal_mcp/applications/sample_tool_app.py +80 -0
- universal_mcp/cli.py +5 -229
- universal_mcp/client/agents/__init__.py +4 -0
- universal_mcp/client/agents/base.py +38 -0
- universal_mcp/client/agents/llm.py +115 -0
- universal_mcp/client/agents/react.py +67 -0
- universal_mcp/client/cli.py +181 -0
- universal_mcp/client/oauth.py +122 -18
- universal_mcp/client/token_store.py +62 -3
- universal_mcp/client/{client.py → transport.py} +127 -48
- universal_mcp/config.py +160 -46
- universal_mcp/exceptions.py +50 -6
- universal_mcp/integrations/__init__.py +1 -4
- universal_mcp/integrations/integration.py +220 -121
- universal_mcp/servers/__init__.py +1 -1
- universal_mcp/servers/server.py +114 -247
- universal_mcp/stores/store.py +126 -93
- universal_mcp/tools/func_metadata.py +1 -1
- universal_mcp/tools/manager.py +15 -3
- universal_mcp/tools/tools.py +2 -2
- universal_mcp/utils/agentr.py +3 -4
- universal_mcp/utils/installation.py +3 -4
- universal_mcp/utils/openapi/api_generator.py +28 -2
- universal_mcp/utils/openapi/api_splitter.py +0 -1
- universal_mcp/utils/openapi/cli.py +243 -0
- universal_mcp/utils/openapi/filters.py +114 -0
- universal_mcp/utils/openapi/openapi.py +31 -2
- universal_mcp/utils/openapi/preprocessor.py +62 -7
- universal_mcp/utils/prompts.py +787 -0
- universal_mcp/utils/singleton.py +4 -1
- universal_mcp/utils/testing.py +6 -6
- universal_mcp-0.1.24rc2.dist-info/METADATA +54 -0
- universal_mcp-0.1.24rc2.dist-info/RECORD +53 -0
- universal_mcp/applications/README.md +0 -122
- universal_mcp/client/__main__.py +0 -30
- universal_mcp/client/agent.py +0 -96
- universal_mcp/integrations/README.md +0 -25
- universal_mcp/servers/README.md +0 -79
- universal_mcp/stores/README.md +0 -74
- universal_mcp/tools/README.md +0 -86
- universal_mcp-0.1.23rc2.dist-info/METADATA +0 -283
- universal_mcp-0.1.23rc2.dist-info/RECORD +0 -51
- /universal_mcp/{utils → tools}/docstring_parser.py +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.23rc2.dist-info → universal_mcp-0.1.24rc2.dist-info}/licenses/LICENSE +0 -0
universal_mcp/analytics.py
CHANGED
@@ -8,6 +8,14 @@ from loguru import logger
|
|
8
8
|
|
9
9
|
|
10
10
|
class Analytics:
|
11
|
+
"""A singleton class for tracking analytics events using PostHog.
|
12
|
+
|
13
|
+
This class handles the initialization of the PostHog client and provides
|
14
|
+
methods to track key events such as application loading and tool execution.
|
15
|
+
Telemetry can be disabled by setting the TELEMETRY_DISABLED environment
|
16
|
+
variable to "true".
|
17
|
+
"""
|
18
|
+
|
11
19
|
_instance = None
|
12
20
|
|
13
21
|
def __new__(cls):
|
@@ -17,7 +25,13 @@ class Analytics:
|
|
17
25
|
return cls._instance
|
18
26
|
|
19
27
|
def _initialize(self):
|
20
|
-
"""
|
28
|
+
"""Initializes the PostHog client and sets up analytics properties.
|
29
|
+
|
30
|
+
This internal method configures the PostHog API key and host.
|
31
|
+
It also determines if analytics should be enabled based on the
|
32
|
+
TELEMETRY_DISABLED environment variable and generates a unique
|
33
|
+
user ID.
|
34
|
+
"""
|
21
35
|
posthog.host = "https://us.i.posthog.com"
|
22
36
|
posthog.api_key = "phc_6HXMDi8CjfIW0l04l34L7IDkpCDeOVz9cOz1KLAHXh8"
|
23
37
|
self.enabled = os.getenv("TELEMETRY_DISABLED", "false").lower() != "true"
|
@@ -26,16 +40,27 @@ class Analytics:
|
|
26
40
|
@staticmethod
|
27
41
|
@lru_cache(maxsize=1)
|
28
42
|
def get_version():
|
29
|
-
"""
|
30
|
-
|
43
|
+
"""Retrieves the installed version of the universal_mcp package.
|
44
|
+
|
45
|
+
Uses importlib.metadata to get the package version.
|
46
|
+
Caches the result for efficiency.
|
47
|
+
|
48
|
+
Returns:
|
49
|
+
str: The package version string, or "unknown" if not found.
|
31
50
|
"""
|
32
51
|
try:
|
33
52
|
return version("universal_mcp")
|
34
|
-
except ImportError:
|
53
|
+
except ImportError: # Should be PackageNotFoundError, but matching existing code
|
35
54
|
return "unknown"
|
36
55
|
|
37
56
|
def track_app_loaded(self, app_name: str):
|
38
|
-
"""
|
57
|
+
"""Tracks an event when an application is successfully loaded.
|
58
|
+
|
59
|
+
This event helps understand which applications are being utilized.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
app_name (str): The name of the application that was loaded.
|
63
|
+
"""
|
39
64
|
if not self.enabled:
|
40
65
|
return
|
41
66
|
try:
|
@@ -53,15 +78,22 @@ class Analytics:
|
|
53
78
|
app_name: str,
|
54
79
|
status: str,
|
55
80
|
error: str = None,
|
56
|
-
user_id=None,
|
81
|
+
user_id=None, # Note: user_id is captured in PostHog but not used from this param
|
57
82
|
):
|
58
|
-
"""
|
83
|
+
"""Tracks an event when a tool is called within an application.
|
84
|
+
|
85
|
+
This event provides insights into tool usage patterns, success rates,
|
86
|
+
and potential errors.
|
59
87
|
|
60
88
|
Args:
|
61
|
-
tool_name:
|
62
|
-
|
63
|
-
|
64
|
-
|
89
|
+
tool_name (str): The name of the tool that was called.
|
90
|
+
app_name (str): The name of the application the tool belongs to.
|
91
|
+
status (str): The status of the tool call (e.g., "success", "error").
|
92
|
+
error (str, optional): The error message if the tool call failed.
|
93
|
+
Defaults to None.
|
94
|
+
user_id (str, optional): An optional user identifier.
|
95
|
+
Note: Currently, the class uses an internally
|
96
|
+
generated user_id for PostHog events.
|
65
97
|
"""
|
66
98
|
if not self.enabled:
|
67
99
|
return
|
@@ -14,25 +14,26 @@ from universal_mcp.integrations import Integration
|
|
14
14
|
|
15
15
|
|
16
16
|
class BaseApplication(ABC):
|
17
|
-
"""
|
18
|
-
Base class for all applications in the Universal MCP system.
|
17
|
+
"""Defines the foundational structure for applications in Universal MCP.
|
19
18
|
|
20
|
-
This abstract base class
|
21
|
-
that all
|
22
|
-
|
19
|
+
This abstract base class (ABC) outlines the common interface and core
|
20
|
+
functionality that all concrete application classes must implement.
|
21
|
+
It handles basic initialization, such as setting the application name,
|
22
|
+
and mandates the implementation of a method to list available tools.
|
23
|
+
Analytics for application loading are also tracked here.
|
23
24
|
|
24
25
|
Attributes:
|
25
|
-
name (str): The name
|
26
|
-
_credentials (Optional[Dict[str, Any]]): Cached credentials for the application
|
26
|
+
name (str): The unique name identifying the application.
|
27
27
|
"""
|
28
28
|
|
29
29
|
def __init__(self, name: str, **kwargs: Any) -> None:
|
30
|
-
"""
|
31
|
-
Initialize the base application.
|
30
|
+
"""Initializes the BaseApplication.
|
32
31
|
|
33
32
|
Args:
|
34
|
-
name: The name
|
35
|
-
**kwargs: Additional keyword arguments
|
33
|
+
name (str): The unique name for this application instance.
|
34
|
+
**kwargs (Any): Additional keyword arguments that might be specific
|
35
|
+
to the concrete application implementation. These are
|
36
|
+
logged but not directly used by BaseApplication.
|
36
37
|
"""
|
37
38
|
self.name = name
|
38
39
|
logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
|
@@ -40,28 +41,35 @@ class BaseApplication(ABC):
|
|
40
41
|
|
41
42
|
@abstractmethod
|
42
43
|
def list_tools(self) -> list[Callable]:
|
43
|
-
"""
|
44
|
-
|
44
|
+
"""Lists all tools provided by this application.
|
45
|
+
|
46
|
+
This method must be implemented by concrete subclasses to return
|
47
|
+
a list of callable tool objects that the application exposes.
|
45
48
|
|
46
49
|
Returns:
|
47
|
-
|
50
|
+
list[Callable]: A list of callable objects, where each callable
|
51
|
+
represents a tool offered by the application.
|
48
52
|
"""
|
49
53
|
pass
|
50
54
|
|
51
55
|
|
52
56
|
class APIApplication(BaseApplication):
|
53
|
-
"""
|
54
|
-
Application that uses HTTP APIs to interact with external services.
|
57
|
+
"""Base class for applications interacting with RESTful HTTP APIs.
|
55
58
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
+
Extends `BaseApplication` to provide functionalities specific to
|
60
|
+
API-based integrations. This includes managing an `httpx.Client`
|
61
|
+
for making HTTP requests, handling authentication headers, processing
|
62
|
+
responses, and offering convenient methods for common HTTP verbs
|
63
|
+
(GET, POST, PUT, DELETE, PATCH).
|
59
64
|
|
60
65
|
Attributes:
|
61
|
-
name (str): The name of the application
|
62
|
-
integration (
|
63
|
-
|
64
|
-
|
66
|
+
name (str): The name of the application.
|
67
|
+
integration (Integration | None): An optional Integration object
|
68
|
+
responsible for managing authentication and credentials.
|
69
|
+
default_timeout (int): The default timeout in seconds for HTTP requests.
|
70
|
+
base_url (str): The base URL for the API endpoint. This should be
|
71
|
+
set by the subclass.
|
72
|
+
_client (httpx.Client | None): The internal httpx client instance.
|
65
73
|
"""
|
66
74
|
|
67
75
|
def __init__(
|
@@ -71,13 +79,17 @@ class APIApplication(BaseApplication):
|
|
71
79
|
client: httpx.Client | None = None,
|
72
80
|
**kwargs: Any,
|
73
81
|
) -> None:
|
74
|
-
"""
|
75
|
-
Initialize the API application.
|
82
|
+
"""Initializes the APIApplication.
|
76
83
|
|
77
84
|
Args:
|
78
|
-
name: The name
|
79
|
-
integration:
|
80
|
-
|
85
|
+
name (str): The unique name for this application instance.
|
86
|
+
integration (Integration | None, optional): An Integration object
|
87
|
+
to handle authentication. Defaults to None.
|
88
|
+
client (httpx.Client | None, optional): An existing httpx.Client
|
89
|
+
instance. If None, a new client will be created on demand.
|
90
|
+
Defaults to None.
|
91
|
+
**kwargs (Any): Additional keyword arguments passed to the
|
92
|
+
BaseApplication.
|
81
93
|
"""
|
82
94
|
super().__init__(name, **kwargs)
|
83
95
|
self.default_timeout: int = 180
|
@@ -87,15 +99,17 @@ class APIApplication(BaseApplication):
|
|
87
99
|
self.base_url: str = ""
|
88
100
|
|
89
101
|
def _get_headers(self) -> dict[str, str]:
|
90
|
-
"""
|
91
|
-
Get the headers for API requests.
|
102
|
+
"""Constructs HTTP headers for API requests based on the integration.
|
92
103
|
|
93
|
-
|
94
|
-
|
95
|
-
|
104
|
+
Retrieves credentials from the configured `integration` and attempts
|
105
|
+
to create appropriate authentication headers. It supports direct header
|
106
|
+
injection, API keys (as Bearer tokens), and access tokens (as Bearer
|
107
|
+
tokens).
|
96
108
|
|
97
109
|
Returns:
|
98
|
-
|
110
|
+
dict[str, str]: A dictionary of HTTP headers. Returns an empty
|
111
|
+
dictionary if no integration is configured or if
|
112
|
+
no suitable credentials are found.
|
99
113
|
"""
|
100
114
|
if not self.integration:
|
101
115
|
logger.debug("No integration configured, returning empty headers")
|
@@ -131,14 +145,15 @@ class APIApplication(BaseApplication):
|
|
131
145
|
|
132
146
|
@property
|
133
147
|
def client(self) -> httpx.Client:
|
134
|
-
"""
|
135
|
-
Get the HTTP client instance.
|
148
|
+
"""Provides an initialized `httpx.Client` instance.
|
136
149
|
|
137
|
-
|
138
|
-
|
150
|
+
If a client was not provided during initialization or has not been
|
151
|
+
created yet, this property will instantiate a new `httpx.Client`.
|
152
|
+
The client is configured with the `base_url` and headers derived
|
153
|
+
from the `_get_headers` method.
|
139
154
|
|
140
155
|
Returns:
|
141
|
-
httpx.Client: The
|
156
|
+
httpx.Client: The active `httpx.Client` instance.
|
142
157
|
"""
|
143
158
|
if not self._client:
|
144
159
|
headers = self._get_headers()
|
@@ -150,22 +165,25 @@ class APIApplication(BaseApplication):
|
|
150
165
|
return self._client
|
151
166
|
|
152
167
|
def _handle_response(self, response: httpx.Response) -> dict[str, Any]:
|
153
|
-
"""
|
154
|
-
Handle API responses by checking for errors and parsing the response appropriately.
|
168
|
+
"""Processes an HTTP response, checking for errors and parsing JSON.
|
155
169
|
|
156
|
-
This method
|
157
|
-
|
158
|
-
|
170
|
+
This method first calls `response.raise_for_status()` to raise an
|
171
|
+
`httpx.HTTPStatusError` if the HTTP request failed. If successful,
|
172
|
+
it attempts to parse the response body as JSON. If JSON parsing
|
173
|
+
fails, it returns a dictionary containing the success status,
|
174
|
+
status code, and raw text of the response.
|
159
175
|
|
160
176
|
Args:
|
161
|
-
response: The HTTP response
|
177
|
+
response (httpx.Response): The HTTP response object from `httpx`.
|
162
178
|
|
163
179
|
Returns:
|
164
|
-
dict[str, Any]
|
165
|
-
|
180
|
+
dict[str, Any]: The parsed JSON response as a dictionary, or
|
181
|
+
a status dictionary if JSON parsing is not possible
|
182
|
+
for a successful response.
|
166
183
|
|
167
184
|
Raises:
|
168
|
-
httpx.HTTPStatusError: If the response
|
185
|
+
httpx.HTTPStatusError: If the HTTP response status code indicates
|
186
|
+
an error (4xx or 5xx).
|
169
187
|
"""
|
170
188
|
response.raise_for_status()
|
171
189
|
try:
|
@@ -174,18 +192,19 @@ class APIApplication(BaseApplication):
|
|
174
192
|
return {"status": "success", "status_code": response.status_code, "text": response.text}
|
175
193
|
|
176
194
|
def _get(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
177
|
-
"""
|
178
|
-
Make a GET request to the specified URL.
|
195
|
+
"""Makes a GET request to the specified URL.
|
179
196
|
|
180
197
|
Args:
|
181
|
-
url: The URL
|
182
|
-
params: Optional query parameters
|
198
|
+
url (str): The URL endpoint for the request (relative to `base_url`).
|
199
|
+
params (dict[str, Any] | None, optional): Optional URL query parameters.
|
200
|
+
Defaults to None.
|
183
201
|
|
184
202
|
Returns:
|
185
|
-
httpx.Response: The raw HTTP response object
|
203
|
+
httpx.Response: The raw HTTP response object. The `_handle_response`
|
204
|
+
method should typically be used to process this.
|
186
205
|
|
187
206
|
Raises:
|
188
|
-
httpx.HTTPStatusError:
|
207
|
+
httpx.HTTPStatusError: Propagated if the underlying client request fails.
|
189
208
|
"""
|
190
209
|
logger.debug(f"Making GET request to {url} with params: {params}")
|
191
210
|
response = self.client.get(url, params=params)
|
@@ -200,26 +219,34 @@ class APIApplication(BaseApplication):
|
|
200
219
|
content_type: str = "application/json",
|
201
220
|
files: dict[str, Any] | None = None,
|
202
221
|
) -> httpx.Response:
|
203
|
-
"""
|
204
|
-
|
222
|
+
"""Makes a POST request to the specified URL.
|
223
|
+
|
224
|
+
Handles different `content_type` values for sending data,
|
225
|
+
including 'application/json', 'application/x-www-form-urlencoded',
|
226
|
+
and 'multipart/form-data' (for file uploads).
|
205
227
|
|
206
228
|
Args:
|
207
|
-
url: The URL
|
208
|
-
data: The data to send
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
229
|
+
url (str): The URL endpoint for the request (relative to `base_url`).
|
230
|
+
data (Any): The data to send in the request body.
|
231
|
+
For 'application/json', this should be a JSON-serializable object.
|
232
|
+
For 'application/x-www-form-urlencoded' or 'multipart/form-data' (if `files` is None),
|
233
|
+
this should be a dictionary of form fields.
|
234
|
+
For other content types (e.g., 'application/octet-stream'), this should be bytes or a string.
|
235
|
+
params (dict[str, Any] | None, optional): Optional URL query parameters.
|
236
|
+
Defaults to None.
|
237
|
+
content_type (str, optional): The Content-Type of the request body.
|
238
|
+
Defaults to "application/json".
|
239
|
+
files (dict[str, Any] | None, optional): A dictionary for file uploads
|
240
|
+
when `content_type` is 'multipart/form-data'.
|
241
|
+
Example: `{'file_field': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}`.
|
242
|
+
Defaults to None.
|
217
243
|
|
218
244
|
Returns:
|
219
|
-
httpx.Response: The raw HTTP response object
|
245
|
+
httpx.Response: The raw HTTP response object. The `_handle_response`
|
246
|
+
method should typically be used to process this.
|
220
247
|
|
221
248
|
Raises:
|
222
|
-
httpx.HTTPStatusError:
|
249
|
+
httpx.HTTPStatusError: Propagated if the underlying client request fails.
|
223
250
|
"""
|
224
251
|
logger.debug(
|
225
252
|
f"Making POST request to {url} with params: {params}, data type: {type(data)}, content_type={content_type}, files: {'yes' if files else 'no'}"
|
@@ -269,26 +296,34 @@ class APIApplication(BaseApplication):
|
|
269
296
|
content_type: str = "application/json",
|
270
297
|
files: dict[str, Any] | None = None,
|
271
298
|
) -> httpx.Response:
|
272
|
-
"""
|
273
|
-
|
299
|
+
"""Makes a PUT request to the specified URL.
|
300
|
+
|
301
|
+
Handles different `content_type` values for sending data,
|
302
|
+
including 'application/json', 'application/x-www-form-urlencoded',
|
303
|
+
and 'multipart/form-data' (for file uploads).
|
274
304
|
|
275
305
|
Args:
|
276
|
-
url: The URL
|
277
|
-
data: The data to send
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
306
|
+
url (str): The URL endpoint for the request (relative to `base_url`).
|
307
|
+
data (Any): The data to send in the request body.
|
308
|
+
For 'application/json', this should be a JSON-serializable object.
|
309
|
+
For 'application/x-www-form-urlencoded' or 'multipart/form-data' (if `files` is None),
|
310
|
+
this should be a dictionary of form fields.
|
311
|
+
For other content types (e.g., 'application/octet-stream'), this should be bytes or a string.
|
312
|
+
params (dict[str, Any] | None, optional): Optional URL query parameters.
|
313
|
+
Defaults to None.
|
314
|
+
content_type (str, optional): The Content-Type of the request body.
|
315
|
+
Defaults to "application/json".
|
316
|
+
files (dict[str, Any] | None, optional): A dictionary for file uploads
|
317
|
+
when `content_type` is 'multipart/form-data'.
|
318
|
+
Example: `{'file_field': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}`.
|
319
|
+
Defaults to None.
|
286
320
|
|
287
321
|
Returns:
|
288
|
-
httpx.Response: The raw HTTP response object
|
322
|
+
httpx.Response: The raw HTTP response object. The `_handle_response`
|
323
|
+
method should typically be used to process this.
|
289
324
|
|
290
325
|
Raises:
|
291
|
-
httpx.HTTPStatusError:
|
326
|
+
httpx.HTTPStatusError: Propagated if the underlying client request fails.
|
292
327
|
"""
|
293
328
|
logger.debug(
|
294
329
|
f"Making PUT request to {url} with params: {params}, data type: {type(data)}, content_type={content_type}, files: {'yes' if files else 'no'}"
|
@@ -332,18 +367,19 @@ class APIApplication(BaseApplication):
|
|
332
367
|
return response
|
333
368
|
|
334
369
|
def _delete(self, url: str, params: dict[str, Any] | None = None) -> httpx.Response:
|
335
|
-
"""
|
336
|
-
Make a DELETE request to the specified URL.
|
370
|
+
"""Makes a DELETE request to the specified URL.
|
337
371
|
|
338
372
|
Args:
|
339
|
-
url: The URL
|
340
|
-
params: Optional query parameters
|
373
|
+
url (str): The URL endpoint for the request (relative to `base_url`).
|
374
|
+
params (dict[str, Any] | None, optional): Optional URL query parameters.
|
375
|
+
Defaults to None.
|
341
376
|
|
342
377
|
Returns:
|
343
|
-
httpx.Response: The raw HTTP response object
|
378
|
+
httpx.Response: The raw HTTP response object. The `_handle_response`
|
379
|
+
method should typically be used to process this.
|
344
380
|
|
345
381
|
Raises:
|
346
|
-
httpx.HTTPStatusError:
|
382
|
+
httpx.HTTPStatusError: Propagated if the underlying client request fails.
|
347
383
|
"""
|
348
384
|
logger.debug(f"Making DELETE request to {url} with params: {params}")
|
349
385
|
response = self.client.delete(url, params=params, timeout=self.default_timeout)
|
@@ -351,19 +387,21 @@ class APIApplication(BaseApplication):
|
|
351
387
|
return response
|
352
388
|
|
353
389
|
def _patch(self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None) -> httpx.Response:
|
354
|
-
"""
|
355
|
-
Make a PATCH request to the specified URL.
|
390
|
+
"""Makes a PATCH request to the specified URL.
|
356
391
|
|
357
392
|
Args:
|
358
|
-
url: The URL
|
359
|
-
data: The data to send in the
|
360
|
-
|
393
|
+
url (str): The URL endpoint for the request (relative to `base_url`).
|
394
|
+
data (dict[str, Any]): The JSON-serializable data to send in the
|
395
|
+
request body.
|
396
|
+
params (dict[str, Any] | None, optional): Optional URL query parameters.
|
397
|
+
Defaults to None.
|
361
398
|
|
362
399
|
Returns:
|
363
|
-
httpx.Response: The raw HTTP response object
|
400
|
+
httpx.Response: The raw HTTP response object. The `_handle_response`
|
401
|
+
method should typically be used to process this.
|
364
402
|
|
365
403
|
Raises:
|
366
|
-
httpx.HTTPStatusError:
|
404
|
+
httpx.HTTPStatusError: Propagated if the underlying client request fails.
|
367
405
|
"""
|
368
406
|
logger.debug(f"Making PATCH request to {url} with params: {params} and data: {data}")
|
369
407
|
response = self.client.patch(
|
@@ -376,17 +414,20 @@ class APIApplication(BaseApplication):
|
|
376
414
|
|
377
415
|
|
378
416
|
class GraphQLApplication(BaseApplication):
|
379
|
-
"""
|
380
|
-
Application that uses GraphQL to interact with external services.
|
417
|
+
"""Base class for applications interacting with GraphQL APIs.
|
381
418
|
|
382
|
-
|
383
|
-
|
384
|
-
and
|
419
|
+
Extends `BaseApplication` to facilitate interactions with services
|
420
|
+
that provide a GraphQL endpoint. It manages a `gql.Client` for
|
421
|
+
executing queries and mutations, handles authentication headers
|
422
|
+
similarly to `APIApplication`, and provides dedicated methods for
|
423
|
+
GraphQL operations.
|
385
424
|
|
386
425
|
Attributes:
|
387
|
-
name (str): The name of the application
|
388
|
-
base_url (str):
|
389
|
-
integration (
|
426
|
+
name (str): The name of the application.
|
427
|
+
base_url (str): The complete URL of the GraphQL endpoint.
|
428
|
+
integration (Integration | None): An optional Integration object
|
429
|
+
for managing authentication.
|
430
|
+
_client (GraphQLClient | None): The internal `gql.Client` instance.
|
390
431
|
"""
|
391
432
|
|
392
433
|
def __init__(
|
@@ -397,14 +438,18 @@ class GraphQLApplication(BaseApplication):
|
|
397
438
|
client: GraphQLClient | None = None,
|
398
439
|
**kwargs: Any,
|
399
440
|
) -> None:
|
400
|
-
"""
|
401
|
-
Initialize the GraphQL application.
|
441
|
+
"""Initializes the GraphQLApplication.
|
402
442
|
|
403
443
|
Args:
|
404
|
-
name: The name
|
405
|
-
base_url: The
|
406
|
-
integration:
|
407
|
-
|
444
|
+
name (str): The unique name for this application instance.
|
445
|
+
base_url (str): The full URL of the GraphQL endpoint.
|
446
|
+
integration (Integration | None, optional): An Integration object
|
447
|
+
to handle authentication. Defaults to None.
|
448
|
+
client (GraphQLClient | None, optional): An existing `gql.Client`
|
449
|
+
instance. If None, a new client will be created on demand.
|
450
|
+
Defaults to None.
|
451
|
+
**kwargs (Any): Additional keyword arguments passed to the
|
452
|
+
BaseApplication.
|
408
453
|
"""
|
409
454
|
super().__init__(name, **kwargs)
|
410
455
|
self.base_url = base_url
|
@@ -413,15 +458,16 @@ class GraphQLApplication(BaseApplication):
|
|
413
458
|
self._client: GraphQLClient | None = client
|
414
459
|
|
415
460
|
def _get_headers(self) -> dict[str, str]:
|
416
|
-
"""
|
417
|
-
Get the headers for GraphQL requests.
|
461
|
+
"""Constructs HTTP headers for GraphQL requests based on the integration.
|
418
462
|
|
419
|
-
|
420
|
-
|
421
|
-
|
463
|
+
Retrieves credentials from the configured `integration` and attempts
|
464
|
+
to create appropriate authentication headers. Primarily supports
|
465
|
+
API keys or access tokens as Bearer tokens in the Authorization header.
|
422
466
|
|
423
467
|
Returns:
|
424
|
-
|
468
|
+
dict[str, str]: A dictionary of HTTP headers. Returns an empty
|
469
|
+
dictionary if no integration is configured or if
|
470
|
+
no suitable credentials are found.
|
425
471
|
"""
|
426
472
|
if not self.integration:
|
427
473
|
logger.debug("No integration configured, returning empty headers")
|
@@ -455,14 +501,16 @@ class GraphQLApplication(BaseApplication):
|
|
455
501
|
|
456
502
|
@property
|
457
503
|
def client(self) -> GraphQLClient:
|
458
|
-
"""
|
459
|
-
Get the GraphQL client instance.
|
504
|
+
"""Provides an initialized `gql.Client` instance.
|
460
505
|
|
461
|
-
|
462
|
-
|
506
|
+
If a client was not provided during initialization or has not been
|
507
|
+
created yet, this property instantiates a new `gql.Client`.
|
508
|
+
The client is configured with a `RequestsHTTPTransport` using the
|
509
|
+
`base_url` and headers from `_get_headers`. It's also set to
|
510
|
+
fetch the schema from the transport.
|
463
511
|
|
464
512
|
Returns:
|
465
|
-
|
513
|
+
GraphQLClient: The active `gql.Client` instance.
|
466
514
|
"""
|
467
515
|
if not self._client:
|
468
516
|
headers = self._get_headers()
|
@@ -475,18 +523,21 @@ class GraphQLApplication(BaseApplication):
|
|
475
523
|
mutation: str | DocumentNode,
|
476
524
|
variables: dict[str, Any] | None = None,
|
477
525
|
) -> dict[str, Any]:
|
478
|
-
"""
|
479
|
-
Execute a GraphQL mutation.
|
526
|
+
"""Executes a GraphQL mutation.
|
480
527
|
|
481
528
|
Args:
|
482
|
-
mutation: The GraphQL mutation string or
|
483
|
-
|
529
|
+
mutation (str | DocumentNode): The GraphQL mutation string or a
|
530
|
+
pre-parsed `gql.DocumentNode` object. If a string is provided,
|
531
|
+
it will be parsed using `gql()`.
|
532
|
+
variables (dict[str, Any] | None, optional): A dictionary of variables
|
533
|
+
to pass with the mutation. Defaults to None.
|
484
534
|
|
485
535
|
Returns:
|
486
|
-
|
536
|
+
dict[str, Any]: The JSON response from the GraphQL server as a dictionary.
|
487
537
|
|
488
538
|
Raises:
|
489
|
-
Exception: If the
|
539
|
+
Exception: If the GraphQL client encounters an error during execution
|
540
|
+
(e.g., network issue, GraphQL server error).
|
490
541
|
"""
|
491
542
|
if isinstance(mutation, str):
|
492
543
|
mutation = gql(mutation)
|
@@ -497,18 +548,21 @@ class GraphQLApplication(BaseApplication):
|
|
497
548
|
query: str | DocumentNode,
|
498
549
|
variables: dict[str, Any] | None = None,
|
499
550
|
) -> dict[str, Any]:
|
500
|
-
"""
|
501
|
-
Execute a GraphQL query.
|
551
|
+
"""Executes a GraphQL query.
|
502
552
|
|
503
553
|
Args:
|
504
|
-
query: The GraphQL query string or
|
505
|
-
|
554
|
+
query (str | DocumentNode): The GraphQL query string or a
|
555
|
+
pre-parsed `gql.DocumentNode` object. If a string is provided,
|
556
|
+
it will be parsed using `gql()`.
|
557
|
+
variables (dict[str, Any] | None, optional): A dictionary of variables
|
558
|
+
to pass with the query. Defaults to None.
|
506
559
|
|
507
560
|
Returns:
|
508
|
-
|
561
|
+
dict[str, Any]: The JSON response from the GraphQL server as a dictionary.
|
509
562
|
|
510
563
|
Raises:
|
511
|
-
Exception: If the
|
564
|
+
Exception: If the GraphQL client encounters an error during execution
|
565
|
+
(e.g., network issue, GraphQL server error).
|
512
566
|
"""
|
513
567
|
if isinstance(query, str):
|
514
568
|
query = gql(query)
|