universal-mcp 0.1.23rc1__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.
Files changed (47) hide show
  1. universal_mcp/analytics.py +43 -11
  2. universal_mcp/applications/application.py +186 -239
  3. universal_mcp/applications/sample_tool_app.py +80 -0
  4. universal_mcp/cli.py +5 -228
  5. universal_mcp/client/agents/__init__.py +4 -0
  6. universal_mcp/client/agents/base.py +38 -0
  7. universal_mcp/client/agents/llm.py +115 -0
  8. universal_mcp/client/agents/react.py +67 -0
  9. universal_mcp/client/cli.py +181 -0
  10. universal_mcp/client/oauth.py +218 -0
  11. universal_mcp/client/token_store.py +91 -0
  12. universal_mcp/client/transport.py +277 -0
  13. universal_mcp/config.py +201 -28
  14. universal_mcp/exceptions.py +50 -6
  15. universal_mcp/integrations/__init__.py +1 -4
  16. universal_mcp/integrations/integration.py +220 -121
  17. universal_mcp/servers/__init__.py +1 -1
  18. universal_mcp/servers/server.py +114 -247
  19. universal_mcp/stores/store.py +126 -93
  20. universal_mcp/tools/adapters.py +16 -0
  21. universal_mcp/tools/func_metadata.py +1 -1
  22. universal_mcp/tools/manager.py +15 -3
  23. universal_mcp/tools/tools.py +2 -2
  24. universal_mcp/utils/agentr.py +3 -4
  25. universal_mcp/utils/installation.py +3 -4
  26. universal_mcp/utils/openapi/api_generator.py +28 -2
  27. universal_mcp/utils/openapi/api_splitter.py +8 -19
  28. universal_mcp/utils/openapi/cli.py +243 -0
  29. universal_mcp/utils/openapi/filters.py +114 -0
  30. universal_mcp/utils/openapi/openapi.py +45 -12
  31. universal_mcp/utils/openapi/preprocessor.py +62 -7
  32. universal_mcp/utils/prompts.py +787 -0
  33. universal_mcp/utils/singleton.py +4 -1
  34. universal_mcp/utils/testing.py +6 -6
  35. universal_mcp-0.1.24rc2.dist-info/METADATA +54 -0
  36. universal_mcp-0.1.24rc2.dist-info/RECORD +53 -0
  37. universal_mcp/applications/README.md +0 -122
  38. universal_mcp/integrations/README.md +0 -25
  39. universal_mcp/servers/README.md +0 -79
  40. universal_mcp/stores/README.md +0 -74
  41. universal_mcp/tools/README.md +0 -86
  42. universal_mcp-0.1.23rc1.dist-info/METADATA +0 -283
  43. universal_mcp-0.1.23rc1.dist-info/RECORD +0 -46
  44. /universal_mcp/{utils → tools}/docstring_parser.py +0 -0
  45. {universal_mcp-0.1.23rc1.dist-info → universal_mcp-0.1.24rc2.dist-info}/WHEEL +0 -0
  46. {universal_mcp-0.1.23rc1.dist-info → universal_mcp-0.1.24rc2.dist-info}/entry_points.txt +0 -0
  47. {universal_mcp-0.1.23rc1.dist-info → universal_mcp-0.1.24rc2.dist-info}/licenses/LICENSE +0 -0
@@ -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 defines the common interface and functionality
21
- that all applications must implement. It provides basic initialization
22
- and credential management capabilities.
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 of the application
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 of the application
35
- **kwargs: Additional keyword arguments passed to the application
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
- List all available tools for the application.
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
- List[Any]: A list of tools available in the application
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
- This class provides a base implementation for applications that communicate
57
- with external services via HTTP APIs. It handles authentication, request
58
- management, and response processing.
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 (Optional[Integration]): The integration configuration
63
- default_timeout (int): Default timeout for HTTP requests in seconds
64
- base_url (str): Base URL for API requests
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 of the application
79
- integration: Optional integration configuration
80
- **kwargs: Additional keyword arguments
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
- This method constructs the appropriate headers based on the available
94
- credentials. It supports various authentication methods including
95
- direct headers, API keys, and access tokens.
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
- Dict[str, str]: Headers to be used in API requests
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
- This property ensures that the HTTP client is properly initialized
138
- with the correct base URL and headers.
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 initialized HTTP client
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
- 1. Checks for API errors and provides detailed error context including status code and response body
158
- 2. For successful responses, automatically parses JSON or returns success message
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 to process
177
+ response (httpx.Response): The HTTP response object from `httpx`.
162
178
 
163
179
  Returns:
164
- dict[str, Any] | str: Parsed JSON data if response contains JSON,
165
- otherwise a success message with status code
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 indicates an error status, with full error details
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 to send the request to
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: If the request fails (when raise_for_status() is called)
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
- Make a POST request to the specified URL.
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 to send the request to
208
- data: The data to send. For 'application/json', this is JSON-serializable.
209
- For 'application/x-www-form-urlencoded' or 'multipart/form-data', this is a dict of form fields.
210
- For other content types, this is raw bytes or string.
211
- params: Optional query parameters
212
- content_type: The Content-Type of the request body.
213
- Examples: 'application/json', 'application/x-www-form-urlencoded',
214
- 'multipart/form-data', 'application/octet-stream', 'text/plain'.
215
- files: Optional dictionary of files to upload for 'multipart/form-data'.
216
- Example: {'file_field_name': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}
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: If the request fails (when raise_for_status() is called)
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
- Make a PUT request to the specified URL.
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 to send the request to
277
- data: The data to send. For 'application/json', this is JSON-serializable.
278
- For 'application/x-www-form-urlencoded' or 'multipart/form-data', this is a dict of form fields.
279
- For other content types, this is raw bytes or string.
280
- params: Optional query parameters
281
- content_type: The Content-Type of the request body.
282
- Examples: 'application/json', 'application/x-www-form-urlencoded',
283
- 'multipart/form-data', 'application/octet-stream', 'text/plain'.
284
- files: Optional dictionary of files to upload for 'multipart/form-data'.
285
- Example: {'file_field_name': ('filename.txt', open('file.txt', 'rb'), 'text/plain')}
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: If the request fails (when raise_for_status() is called)
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 to send the request to
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: If the request fails (when raise_for_status() is called)
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 to send the request to
359
- data: The data to send in the request body
360
- params: Optional query parameters
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: If the request fails (when raise_for_status() is called)
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(
@@ -374,126 +412,22 @@ class APIApplication(BaseApplication):
374
412
  logger.debug(f"PATCH request successful with status code: {response.status_code}")
375
413
  return response
376
414
 
377
- # New convenience methods that handle responses automatically with enhanced error handling
378
- def _get_json(self, url: str, params: dict[str, Any] | None = None) -> dict[str, Any] | str:
379
- """
380
- Make a GET request and automatically handle the response with enhanced error handling.
381
-
382
- Args:
383
- url: The URL to send the request to
384
- params: Optional query parameters
385
-
386
- Returns:
387
- dict[str, Any] | str: Parsed JSON response if available, otherwise success message
388
-
389
- Raises:
390
- httpx.HTTPStatusError: If the request fails with detailed error information including response body
391
- """
392
- response = self._get(url, params)
393
- return self._handle_response(response)
394
-
395
- def _post_json(
396
- self,
397
- url: str,
398
- data: Any,
399
- params: dict[str, Any] | None = None,
400
- content_type: str = "application/json",
401
- files: dict[str, Any] | None = None,
402
- ) -> dict[str, Any] | str:
403
- """
404
- Make a POST request and automatically handle the response with enhanced error handling.
405
-
406
- Args:
407
- url: The URL to send the request to
408
- data: The data to send
409
- params: Optional query parameters
410
- content_type: The Content-Type of the request body
411
- files: Optional dictionary of files to upload
412
-
413
- Returns:
414
- dict[str, Any] | str: Parsed JSON response if available, otherwise success message
415
-
416
- Raises:
417
- httpx.HTTPStatusError: If the request fails with detailed error information including response body
418
- """
419
- response = self._post(url, data, params, content_type, files)
420
- return self._handle_response(response)
421
-
422
- def _put_json(
423
- self,
424
- url: str,
425
- data: Any,
426
- params: dict[str, Any] | None = None,
427
- content_type: str = "application/json",
428
- files: dict[str, Any] | None = None,
429
- ) -> dict[str, Any] | str:
430
- """
431
- Make a PUT request and automatically handle the response with enhanced error handling.
432
-
433
- Args:
434
- url: The URL to send the request to
435
- data: The data to send
436
- params: Optional query parameters
437
- content_type: The Content-Type of the request body
438
- files: Optional dictionary of files to upload
439
-
440
- Returns:
441
- dict[str, Any] | str: Parsed JSON response if available, otherwise success message
442
-
443
- Raises:
444
- httpx.HTTPStatusError: If the request fails with detailed error information including response body
445
- """
446
- response = self._put(url, data, params, content_type, files)
447
- return self._handle_response(response)
448
-
449
- def _delete_json(self, url: str, params: dict[str, Any] | None = None) -> dict[str, Any] | str:
450
- """
451
- Make a DELETE request and automatically handle the response with enhanced error handling.
452
-
453
- Args:
454
- url: The URL to send the request to
455
- params: Optional query parameters
456
-
457
- Returns:
458
- dict[str, Any] | str: Parsed JSON response if available, otherwise success message
459
-
460
- Raises:
461
- httpx.HTTPStatusError: If the request fails with detailed error information including response body
462
- """
463
- response = self._delete(url, params)
464
- return self._handle_response(response)
465
-
466
- def _patch_json(self, url: str, data: dict[str, Any], params: dict[str, Any] | None = None) -> dict[str, Any] | str:
467
- """
468
- Make a PATCH request and automatically handle the response with enhanced error handling.
469
-
470
- Args:
471
- url: The URL to send the request to
472
- data: The data to send in the request body
473
- params: Optional query parameters
474
-
475
- Returns:
476
- dict[str, Any] | str: Parsed JSON response if available, otherwise success message
477
-
478
- Raises:
479
- httpx.HTTPStatusError: If the request fails with detailed error information including response body
480
- """
481
- response = self._patch(url, data, params)
482
- return self._handle_response(response)
483
-
484
415
 
485
416
  class GraphQLApplication(BaseApplication):
486
- """
487
- Application that uses GraphQL to interact with external services.
417
+ """Base class for applications interacting with GraphQL APIs.
488
418
 
489
- This class provides a base implementation for applications that communicate
490
- with external services via GraphQL. It handles authentication, query execution,
491
- and response processing.
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.
492
424
 
493
425
  Attributes:
494
- name (str): The name of the application
495
- base_url (str): Base URL for GraphQL endpoint
496
- integration (Optional[Integration]): The integration configuration
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.
497
431
  """
498
432
 
499
433
  def __init__(
@@ -504,14 +438,18 @@ class GraphQLApplication(BaseApplication):
504
438
  client: GraphQLClient | None = None,
505
439
  **kwargs: Any,
506
440
  ) -> None:
507
- """
508
- Initialize the GraphQL application.
441
+ """Initializes the GraphQLApplication.
509
442
 
510
443
  Args:
511
- name: The name of the application
512
- base_url: The base URL for the GraphQL endpoint
513
- integration: Optional integration configuration
514
- **kwargs: Additional keyword arguments
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.
515
453
  """
516
454
  super().__init__(name, **kwargs)
517
455
  self.base_url = base_url
@@ -520,15 +458,16 @@ class GraphQLApplication(BaseApplication):
520
458
  self._client: GraphQLClient | None = client
521
459
 
522
460
  def _get_headers(self) -> dict[str, str]:
523
- """
524
- Get the headers for GraphQL requests.
461
+ """Constructs HTTP headers for GraphQL requests based on the integration.
525
462
 
526
- This method constructs the appropriate headers based on the available
527
- credentials. It supports various authentication methods including
528
- direct headers, API keys, and access tokens.
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.
529
466
 
530
467
  Returns:
531
- Dict[str, str]: Headers to be used in GraphQL requests
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.
532
471
  """
533
472
  if not self.integration:
534
473
  logger.debug("No integration configured, returning empty headers")
@@ -562,14 +501,16 @@ class GraphQLApplication(BaseApplication):
562
501
 
563
502
  @property
564
503
  def client(self) -> GraphQLClient:
565
- """
566
- Get the GraphQL client instance.
504
+ """Provides an initialized `gql.Client` instance.
567
505
 
568
- This property ensures that the GraphQL client is properly initialized
569
- with the correct transport and headers.
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.
570
511
 
571
512
  Returns:
572
- Client: The initialized GraphQL client
513
+ GraphQLClient: The active `gql.Client` instance.
573
514
  """
574
515
  if not self._client:
575
516
  headers = self._get_headers()
@@ -582,18 +523,21 @@ class GraphQLApplication(BaseApplication):
582
523
  mutation: str | DocumentNode,
583
524
  variables: dict[str, Any] | None = None,
584
525
  ) -> dict[str, Any]:
585
- """
586
- Execute a GraphQL mutation.
526
+ """Executes a GraphQL mutation.
587
527
 
588
528
  Args:
589
- mutation: The GraphQL mutation string or DocumentNode
590
- variables: Optional variables for the mutation
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.
591
534
 
592
535
  Returns:
593
- Dict[str, Any]: The result of the mutation
536
+ dict[str, Any]: The JSON response from the GraphQL server as a dictionary.
594
537
 
595
538
  Raises:
596
- Exception: If the mutation execution fails
539
+ Exception: If the GraphQL client encounters an error during execution
540
+ (e.g., network issue, GraphQL server error).
597
541
  """
598
542
  if isinstance(mutation, str):
599
543
  mutation = gql(mutation)
@@ -604,18 +548,21 @@ class GraphQLApplication(BaseApplication):
604
548
  query: str | DocumentNode,
605
549
  variables: dict[str, Any] | None = None,
606
550
  ) -> dict[str, Any]:
607
- """
608
- Execute a GraphQL query.
551
+ """Executes a GraphQL query.
609
552
 
610
553
  Args:
611
- query: The GraphQL query string or DocumentNode
612
- variables: Optional variables for the query
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.
613
559
 
614
560
  Returns:
615
- Dict[str, Any]: The result of the query
561
+ dict[str, Any]: The JSON response from the GraphQL server as a dictionary.
616
562
 
617
563
  Raises:
618
- Exception: If the query execution fails
564
+ Exception: If the GraphQL client encounters an error during execution
565
+ (e.g., network issue, GraphQL server error).
619
566
  """
620
567
  if isinstance(query, str):
621
568
  query = gql(query)