kailash 0.1.0__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.
- kailash/__init__.py +31 -0
- kailash/__main__.py +11 -0
- kailash/cli/__init__.py +5 -0
- kailash/cli/commands.py +563 -0
- kailash/manifest.py +778 -0
- kailash/nodes/__init__.py +23 -0
- kailash/nodes/ai/__init__.py +26 -0
- kailash/nodes/ai/agents.py +417 -0
- kailash/nodes/ai/models.py +488 -0
- kailash/nodes/api/__init__.py +52 -0
- kailash/nodes/api/auth.py +567 -0
- kailash/nodes/api/graphql.py +480 -0
- kailash/nodes/api/http.py +598 -0
- kailash/nodes/api/rate_limiting.py +572 -0
- kailash/nodes/api/rest.py +665 -0
- kailash/nodes/base.py +1032 -0
- kailash/nodes/base_async.py +128 -0
- kailash/nodes/code/__init__.py +32 -0
- kailash/nodes/code/python.py +1021 -0
- kailash/nodes/data/__init__.py +125 -0
- kailash/nodes/data/readers.py +496 -0
- kailash/nodes/data/sharepoint_graph.py +623 -0
- kailash/nodes/data/sql.py +380 -0
- kailash/nodes/data/streaming.py +1168 -0
- kailash/nodes/data/vector_db.py +964 -0
- kailash/nodes/data/writers.py +529 -0
- kailash/nodes/logic/__init__.py +6 -0
- kailash/nodes/logic/async_operations.py +702 -0
- kailash/nodes/logic/operations.py +551 -0
- kailash/nodes/transform/__init__.py +5 -0
- kailash/nodes/transform/processors.py +379 -0
- kailash/runtime/__init__.py +6 -0
- kailash/runtime/async_local.py +356 -0
- kailash/runtime/docker.py +697 -0
- kailash/runtime/local.py +434 -0
- kailash/runtime/parallel.py +557 -0
- kailash/runtime/runner.py +110 -0
- kailash/runtime/testing.py +347 -0
- kailash/sdk_exceptions.py +307 -0
- kailash/tracking/__init__.py +7 -0
- kailash/tracking/manager.py +885 -0
- kailash/tracking/metrics_collector.py +342 -0
- kailash/tracking/models.py +535 -0
- kailash/tracking/storage/__init__.py +0 -0
- kailash/tracking/storage/base.py +113 -0
- kailash/tracking/storage/database.py +619 -0
- kailash/tracking/storage/filesystem.py +543 -0
- kailash/utils/__init__.py +0 -0
- kailash/utils/export.py +924 -0
- kailash/utils/templates.py +680 -0
- kailash/visualization/__init__.py +62 -0
- kailash/visualization/api.py +732 -0
- kailash/visualization/dashboard.py +951 -0
- kailash/visualization/performance.py +808 -0
- kailash/visualization/reports.py +1471 -0
- kailash/workflow/__init__.py +15 -0
- kailash/workflow/builder.py +245 -0
- kailash/workflow/graph.py +827 -0
- kailash/workflow/mermaid_visualizer.py +628 -0
- kailash/workflow/mock_registry.py +63 -0
- kailash/workflow/runner.py +302 -0
- kailash/workflow/state.py +238 -0
- kailash/workflow/visualization.py +588 -0
- kailash-0.1.0.dist-info/METADATA +710 -0
- kailash-0.1.0.dist-info/RECORD +69 -0
- kailash-0.1.0.dist-info/WHEEL +5 -0
- kailash-0.1.0.dist-info/entry_points.txt +2 -0
- kailash-0.1.0.dist-info/licenses/LICENSE +21 -0
- kailash-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,480 @@
|
|
1
|
+
"""GraphQL client nodes for the Kailash SDK.
|
2
|
+
|
3
|
+
This module provides specialized nodes for interacting with GraphQL APIs in both
|
4
|
+
synchronous and asynchronous modes. These nodes provide a higher-level interface
|
5
|
+
for constructing and executing GraphQL queries.
|
6
|
+
|
7
|
+
Key Components:
|
8
|
+
- GraphQLClientNode: Synchronous GraphQL API client
|
9
|
+
- AsyncGraphQLClientNode: Asynchronous GraphQL API client
|
10
|
+
- GraphQL query building and response handling utilities
|
11
|
+
"""
|
12
|
+
|
13
|
+
from typing import Any, Dict, Optional
|
14
|
+
|
15
|
+
from kailash.nodes.api.http import AsyncHTTPRequestNode, HTTPRequestNode
|
16
|
+
from kailash.nodes.base import Node, NodeParameter, register_node
|
17
|
+
from kailash.nodes.base_async import AsyncNode
|
18
|
+
from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
|
19
|
+
|
20
|
+
|
21
|
+
@register_node(alias="GraphQLClient")
|
22
|
+
class GraphQLClientNode(Node):
|
23
|
+
"""Node for interacting with GraphQL APIs.
|
24
|
+
|
25
|
+
This node provides a specialized interface for executing GraphQL queries
|
26
|
+
and mutations, with support for:
|
27
|
+
- Query and mutation operations
|
28
|
+
- Variables and fragments
|
29
|
+
- Response selection and formatting
|
30
|
+
- Error handling for GraphQL-specific error formats
|
31
|
+
|
32
|
+
Design Purpose:
|
33
|
+
- Simplify GraphQL API integration in workflows
|
34
|
+
- Abstract away GraphQL-specific protocol details
|
35
|
+
- Provide type-safe variable handling
|
36
|
+
- Support all GraphQL operations and features
|
37
|
+
|
38
|
+
Upstream Usage:
|
39
|
+
- Workflow: Creates and configures for specific GraphQL APIs
|
40
|
+
- API integration workflows: Uses for external service integration
|
41
|
+
|
42
|
+
Downstream Consumers:
|
43
|
+
- Data processing nodes: Consume API response data
|
44
|
+
- Custom nodes: Process API-specific data formats
|
45
|
+
"""
|
46
|
+
|
47
|
+
def __init__(self, **kwargs):
|
48
|
+
"""Initialize the GraphQL client node.
|
49
|
+
|
50
|
+
Args:
|
51
|
+
endpoint (str): GraphQL endpoint URL
|
52
|
+
headers (dict, optional): Default headers for all requests
|
53
|
+
auth (dict, optional): Authentication configuration
|
54
|
+
timeout (int, optional): Default request timeout in seconds
|
55
|
+
verify_ssl (bool, optional): Whether to verify SSL certificates
|
56
|
+
retry_count (int, optional): Number of times to retry failed requests
|
57
|
+
retry_backoff (float, optional): Backoff factor for retries
|
58
|
+
**kwargs: Additional parameters passed to base Node
|
59
|
+
"""
|
60
|
+
super().__init__(**kwargs)
|
61
|
+
self.http_node = HTTPRequestNode(**kwargs)
|
62
|
+
|
63
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
64
|
+
"""Define the parameters this node accepts.
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
Dictionary of parameter definitions
|
68
|
+
"""
|
69
|
+
return {
|
70
|
+
"endpoint": NodeParameter(
|
71
|
+
name="endpoint",
|
72
|
+
type=str,
|
73
|
+
required=True,
|
74
|
+
description="GraphQL endpoint URL",
|
75
|
+
),
|
76
|
+
"query": NodeParameter(
|
77
|
+
name="query",
|
78
|
+
type=str,
|
79
|
+
required=True,
|
80
|
+
description="GraphQL query or mutation string",
|
81
|
+
),
|
82
|
+
"variables": NodeParameter(
|
83
|
+
name="variables",
|
84
|
+
type=dict,
|
85
|
+
required=False,
|
86
|
+
default={},
|
87
|
+
description="Variables for the GraphQL query",
|
88
|
+
),
|
89
|
+
"operation_name": NodeParameter(
|
90
|
+
name="operation_name",
|
91
|
+
type=str,
|
92
|
+
required=False,
|
93
|
+
default=None,
|
94
|
+
description="Name of the operation to execute (if query contains multiple)",
|
95
|
+
),
|
96
|
+
"headers": NodeParameter(
|
97
|
+
name="headers",
|
98
|
+
type=dict,
|
99
|
+
required=False,
|
100
|
+
default={},
|
101
|
+
description="HTTP headers to include in the request",
|
102
|
+
),
|
103
|
+
"timeout": NodeParameter(
|
104
|
+
name="timeout",
|
105
|
+
type=int,
|
106
|
+
required=False,
|
107
|
+
default=30,
|
108
|
+
description="Request timeout in seconds",
|
109
|
+
),
|
110
|
+
"verify_ssl": NodeParameter(
|
111
|
+
name="verify_ssl",
|
112
|
+
type=bool,
|
113
|
+
required=False,
|
114
|
+
default=True,
|
115
|
+
description="Whether to verify SSL certificates",
|
116
|
+
),
|
117
|
+
"retry_count": NodeParameter(
|
118
|
+
name="retry_count",
|
119
|
+
type=int,
|
120
|
+
required=False,
|
121
|
+
default=0,
|
122
|
+
description="Number of times to retry failed requests",
|
123
|
+
),
|
124
|
+
"retry_backoff": NodeParameter(
|
125
|
+
name="retry_backoff",
|
126
|
+
type=float,
|
127
|
+
required=False,
|
128
|
+
default=0.5,
|
129
|
+
description="Backoff factor for retries",
|
130
|
+
),
|
131
|
+
"flatten_response": NodeParameter(
|
132
|
+
name="flatten_response",
|
133
|
+
type=bool,
|
134
|
+
required=False,
|
135
|
+
default=False,
|
136
|
+
description="Whether to flatten the response data structure",
|
137
|
+
),
|
138
|
+
}
|
139
|
+
|
140
|
+
def get_output_schema(self) -> Dict[str, NodeParameter]:
|
141
|
+
"""Define the output schema for this node.
|
142
|
+
|
143
|
+
Returns:
|
144
|
+
Dictionary of output parameter definitions
|
145
|
+
"""
|
146
|
+
return {
|
147
|
+
"data": NodeParameter(
|
148
|
+
name="data",
|
149
|
+
type=Any,
|
150
|
+
required=True,
|
151
|
+
description="GraphQL response data",
|
152
|
+
),
|
153
|
+
"errors": NodeParameter(
|
154
|
+
name="errors",
|
155
|
+
type=list,
|
156
|
+
required=False,
|
157
|
+
description="GraphQL errors (if any)",
|
158
|
+
),
|
159
|
+
"success": NodeParameter(
|
160
|
+
name="success",
|
161
|
+
type=bool,
|
162
|
+
required=True,
|
163
|
+
description="Whether the request was successful (no errors)",
|
164
|
+
),
|
165
|
+
"metadata": NodeParameter(
|
166
|
+
name="metadata",
|
167
|
+
type=dict,
|
168
|
+
required=True,
|
169
|
+
description="Additional metadata about the request and response",
|
170
|
+
),
|
171
|
+
}
|
172
|
+
|
173
|
+
def _build_graphql_payload(
|
174
|
+
self,
|
175
|
+
query: str,
|
176
|
+
variables: Dict[str, Any] = None,
|
177
|
+
operation_name: Optional[str] = None,
|
178
|
+
) -> Dict[str, Any]:
|
179
|
+
"""Build a GraphQL request payload.
|
180
|
+
|
181
|
+
Args:
|
182
|
+
query: GraphQL query or mutation string
|
183
|
+
variables: Variables for the query
|
184
|
+
operation_name: Name of the operation to execute
|
185
|
+
|
186
|
+
Returns:
|
187
|
+
Dictionary containing the GraphQL request payload
|
188
|
+
"""
|
189
|
+
payload = {"query": query}
|
190
|
+
|
191
|
+
if variables:
|
192
|
+
payload["variables"] = variables
|
193
|
+
|
194
|
+
if operation_name:
|
195
|
+
payload["operationName"] = operation_name
|
196
|
+
|
197
|
+
return payload
|
198
|
+
|
199
|
+
def _process_graphql_response(
|
200
|
+
self, response: Dict[str, Any], flatten_response: bool = False
|
201
|
+
) -> Dict[str, Any]:
|
202
|
+
"""Process a GraphQL response.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
response: Raw HTTP response from GraphQL API
|
206
|
+
flatten_response: Whether to flatten the response data structure
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
Processed GraphQL response with data and errors
|
210
|
+
|
211
|
+
Raises:
|
212
|
+
NodeExecutionError: If the response has an invalid format
|
213
|
+
"""
|
214
|
+
content = response.get("content", {})
|
215
|
+
|
216
|
+
# Validate GraphQL response format
|
217
|
+
if not isinstance(content, dict):
|
218
|
+
raise NodeExecutionError(
|
219
|
+
f"Invalid GraphQL response format: expected dict, got {type(content).__name__}"
|
220
|
+
)
|
221
|
+
|
222
|
+
# Check for transport-level errors (non-200 responses)
|
223
|
+
if response.get("status_code", 200) >= 400:
|
224
|
+
error_msg = "GraphQL request failed with status code " + str(
|
225
|
+
response.get("status_code")
|
226
|
+
)
|
227
|
+
if isinstance(content, dict) and "errors" not in content:
|
228
|
+
# Add transport error to GraphQL error format
|
229
|
+
content = {"data": None, "errors": [{"message": error_msg}]}
|
230
|
+
|
231
|
+
# Extract data and errors
|
232
|
+
data = content.get("data")
|
233
|
+
errors = content.get("errors", [])
|
234
|
+
success = response.get("status_code", 200) < 400 and not errors
|
235
|
+
|
236
|
+
# Optional: Flatten data structure
|
237
|
+
if flatten_response and data and isinstance(data, dict):
|
238
|
+
# Get the first key in data (usually operation name) and return its value
|
239
|
+
if len(data) == 1:
|
240
|
+
data = next(iter(data.values()))
|
241
|
+
|
242
|
+
return {"data": data, "errors": errors, "success": success}
|
243
|
+
|
244
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
245
|
+
"""Execute a GraphQL query or mutation.
|
246
|
+
|
247
|
+
Args:
|
248
|
+
endpoint (str): GraphQL endpoint URL
|
249
|
+
query (str): GraphQL query or mutation string
|
250
|
+
variables (dict, optional): Variables for the query
|
251
|
+
operation_name (str, optional): Name of the operation to execute
|
252
|
+
headers (dict, optional): HTTP headers
|
253
|
+
timeout (int, optional): Request timeout in seconds
|
254
|
+
verify_ssl (bool, optional): Whether to verify SSL certificates
|
255
|
+
retry_count (int, optional): Number of times to retry failed requests
|
256
|
+
retry_backoff (float, optional): Backoff factor for retries
|
257
|
+
flatten_response (bool, optional): Whether to flatten the response
|
258
|
+
|
259
|
+
Returns:
|
260
|
+
Dictionary containing:
|
261
|
+
data: GraphQL response data
|
262
|
+
errors: List of GraphQL errors (if any)
|
263
|
+
success: Boolean indicating request success
|
264
|
+
metadata: Additional request/response metadata
|
265
|
+
|
266
|
+
Raises:
|
267
|
+
NodeValidationError: If required parameters are missing or invalid
|
268
|
+
NodeExecutionError: If the request fails
|
269
|
+
"""
|
270
|
+
endpoint = kwargs.get("endpoint")
|
271
|
+
query = kwargs.get("query")
|
272
|
+
variables = kwargs.get("variables", {})
|
273
|
+
operation_name = kwargs.get("operation_name")
|
274
|
+
headers = kwargs.get("headers", {})
|
275
|
+
timeout = kwargs.get("timeout", 30)
|
276
|
+
verify_ssl = kwargs.get("verify_ssl", True)
|
277
|
+
retry_count = kwargs.get("retry_count", 0)
|
278
|
+
retry_backoff = kwargs.get("retry_backoff", 0.5)
|
279
|
+
flatten_response = kwargs.get("flatten_response", False)
|
280
|
+
|
281
|
+
# Validate required parameters
|
282
|
+
if not endpoint:
|
283
|
+
raise NodeValidationError("GraphQL endpoint URL is required")
|
284
|
+
|
285
|
+
if not query:
|
286
|
+
raise NodeValidationError("GraphQL query is required")
|
287
|
+
|
288
|
+
# Set content type for GraphQL requests
|
289
|
+
if "Content-Type" not in headers:
|
290
|
+
headers["Content-Type"] = "application/json"
|
291
|
+
|
292
|
+
# Build GraphQL payload
|
293
|
+
payload = self._build_graphql_payload(query, variables, operation_name)
|
294
|
+
|
295
|
+
# Execute HTTP request
|
296
|
+
self.logger.info(f"Executing GraphQL request to {endpoint}")
|
297
|
+
if variables:
|
298
|
+
self.logger.debug(f"With variables: {variables}")
|
299
|
+
|
300
|
+
http_params = {
|
301
|
+
"url": endpoint,
|
302
|
+
"method": "POST",
|
303
|
+
"headers": headers,
|
304
|
+
"json_data": payload,
|
305
|
+
"response_format": "json",
|
306
|
+
"timeout": timeout,
|
307
|
+
"verify_ssl": verify_ssl,
|
308
|
+
"retry_count": retry_count,
|
309
|
+
"retry_backoff": retry_backoff,
|
310
|
+
}
|
311
|
+
|
312
|
+
http_result = self.http_node.run(**http_params)
|
313
|
+
|
314
|
+
# Process GraphQL-specific response
|
315
|
+
response = http_result["response"]
|
316
|
+
graphql_result = self._process_graphql_response(response, flatten_response)
|
317
|
+
|
318
|
+
# Construct metadata
|
319
|
+
metadata = {
|
320
|
+
"endpoint": endpoint,
|
321
|
+
"operation_name": operation_name,
|
322
|
+
"response_time_ms": response["response_time_ms"],
|
323
|
+
"status_code": response["status_code"],
|
324
|
+
}
|
325
|
+
|
326
|
+
return {
|
327
|
+
"data": graphql_result["data"],
|
328
|
+
"errors": graphql_result["errors"],
|
329
|
+
"success": graphql_result["success"],
|
330
|
+
"metadata": metadata,
|
331
|
+
}
|
332
|
+
|
333
|
+
|
334
|
+
@register_node(alias="AsyncGraphQLClient")
|
335
|
+
class AsyncGraphQLClientNode(AsyncNode):
|
336
|
+
"""Asynchronous node for interacting with GraphQL APIs.
|
337
|
+
|
338
|
+
This node provides the same functionality as GraphQLClientNode but uses
|
339
|
+
asynchronous I/O for better performance, especially for concurrent requests.
|
340
|
+
|
341
|
+
Design Purpose:
|
342
|
+
- Enable efficient, non-blocking GraphQL operations in workflows
|
343
|
+
- Provide the same interface as GraphQLClientNode but with async execution
|
344
|
+
- Support high-throughput API integrations with minimal overhead
|
345
|
+
|
346
|
+
Upstream Usage:
|
347
|
+
- AsyncLocalRuntime: Executes workflow with async support
|
348
|
+
- Specialized async API nodes: May extend this node
|
349
|
+
|
350
|
+
Downstream Consumers:
|
351
|
+
- Data processing nodes: Consume API response data
|
352
|
+
- Decision nodes: Route workflow based on API responses
|
353
|
+
"""
|
354
|
+
|
355
|
+
def __init__(self, **kwargs):
|
356
|
+
"""Initialize the async GraphQL client node.
|
357
|
+
|
358
|
+
Args:
|
359
|
+
Same as GraphQLClientNode
|
360
|
+
"""
|
361
|
+
super().__init__(**kwargs)
|
362
|
+
self.http_node = AsyncHTTPRequestNode(**kwargs)
|
363
|
+
self.graphql_node = GraphQLClientNode(**kwargs)
|
364
|
+
|
365
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
366
|
+
"""Define the parameters this node accepts.
|
367
|
+
|
368
|
+
Returns:
|
369
|
+
Dictionary of parameter definitions
|
370
|
+
"""
|
371
|
+
# Same parameters as the synchronous version
|
372
|
+
return self.graphql_node.get_parameters()
|
373
|
+
|
374
|
+
def get_output_schema(self) -> Dict[str, NodeParameter]:
|
375
|
+
"""Define the output schema for this node.
|
376
|
+
|
377
|
+
Returns:
|
378
|
+
Dictionary of output parameter definitions
|
379
|
+
"""
|
380
|
+
# Same output schema as the synchronous version
|
381
|
+
return self.graphql_node.get_output_schema()
|
382
|
+
|
383
|
+
def run(self, **kwargs) -> Dict[str, Any]:
|
384
|
+
"""Synchronous version of the GraphQL request, for compatibility.
|
385
|
+
|
386
|
+
This is implemented for compatibility but users should use the
|
387
|
+
async_run method for better performance.
|
388
|
+
|
389
|
+
Args:
|
390
|
+
Same as GraphQLClientNode.run()
|
391
|
+
|
392
|
+
Returns:
|
393
|
+
Same as GraphQLClientNode.run()
|
394
|
+
|
395
|
+
Raises:
|
396
|
+
NodeValidationError: If required parameters are missing or invalid
|
397
|
+
NodeExecutionError: If the request fails
|
398
|
+
"""
|
399
|
+
# Forward to the synchronous GraphQL node
|
400
|
+
return self.graphql_node.run(**kwargs)
|
401
|
+
|
402
|
+
async def async_run(self, **kwargs) -> Dict[str, Any]:
|
403
|
+
"""Execute a GraphQL query or mutation asynchronously.
|
404
|
+
|
405
|
+
Args:
|
406
|
+
Same as GraphQLClientNode.run()
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
Same as GraphQLClientNode.run()
|
410
|
+
|
411
|
+
Raises:
|
412
|
+
NodeValidationError: If required parameters are missing or invalid
|
413
|
+
NodeExecutionError: If the request fails
|
414
|
+
"""
|
415
|
+
endpoint = kwargs.get("endpoint")
|
416
|
+
query = kwargs.get("query")
|
417
|
+
variables = kwargs.get("variables", {})
|
418
|
+
operation_name = kwargs.get("operation_name")
|
419
|
+
headers = kwargs.get("headers", {})
|
420
|
+
timeout = kwargs.get("timeout", 30)
|
421
|
+
verify_ssl = kwargs.get("verify_ssl", True)
|
422
|
+
retry_count = kwargs.get("retry_count", 0)
|
423
|
+
retry_backoff = kwargs.get("retry_backoff", 0.5)
|
424
|
+
flatten_response = kwargs.get("flatten_response", False)
|
425
|
+
|
426
|
+
# Validate required parameters
|
427
|
+
if not endpoint:
|
428
|
+
raise NodeValidationError("GraphQL endpoint URL is required")
|
429
|
+
|
430
|
+
if not query:
|
431
|
+
raise NodeValidationError("GraphQL query is required")
|
432
|
+
|
433
|
+
# Set content type for GraphQL requests
|
434
|
+
if "Content-Type" not in headers:
|
435
|
+
headers["Content-Type"] = "application/json"
|
436
|
+
|
437
|
+
# Build GraphQL payload
|
438
|
+
payload = self.graphql_node._build_graphql_payload(
|
439
|
+
query, variables, operation_name
|
440
|
+
)
|
441
|
+
|
442
|
+
# Execute HTTP request asynchronously
|
443
|
+
self.logger.info(f"Executing async GraphQL request to {endpoint}")
|
444
|
+
if variables:
|
445
|
+
self.logger.debug(f"With variables: {variables}")
|
446
|
+
|
447
|
+
http_params = {
|
448
|
+
"url": endpoint,
|
449
|
+
"method": "POST",
|
450
|
+
"headers": headers,
|
451
|
+
"json_data": payload,
|
452
|
+
"response_format": "json",
|
453
|
+
"timeout": timeout,
|
454
|
+
"verify_ssl": verify_ssl,
|
455
|
+
"retry_count": retry_count,
|
456
|
+
"retry_backoff": retry_backoff,
|
457
|
+
}
|
458
|
+
|
459
|
+
http_result = await self.http_node.async_run(**http_params)
|
460
|
+
|
461
|
+
# Process GraphQL-specific response
|
462
|
+
response = http_result["response"]
|
463
|
+
graphql_result = self.graphql_node._process_graphql_response(
|
464
|
+
response, flatten_response
|
465
|
+
)
|
466
|
+
|
467
|
+
# Construct metadata
|
468
|
+
metadata = {
|
469
|
+
"endpoint": endpoint,
|
470
|
+
"operation_name": operation_name,
|
471
|
+
"response_time_ms": response["response_time_ms"],
|
472
|
+
"status_code": response["status_code"],
|
473
|
+
}
|
474
|
+
|
475
|
+
return {
|
476
|
+
"data": graphql_result["data"],
|
477
|
+
"errors": graphql_result["errors"],
|
478
|
+
"success": graphql_result["success"],
|
479
|
+
"metadata": metadata,
|
480
|
+
}
|