From 7f5cc9671cb9ecdb0459d7ad3464f2d5939a0623 Mon Sep 17 00:00:00 2001 From: Cyber MacGeddon Date: Wed, 3 Sep 2025 22:32:48 +0100 Subject: [PATCH] GraphQL query utility --- trustgraph-base/trustgraph/api/flow.py | 43 ++++++ trustgraph-cli/pyproject.toml | 1 + .../trustgraph/cli/invoke_objects_query.py | 124 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 trustgraph-cli/trustgraph/cli/invoke_objects_query.py diff --git a/trustgraph-base/trustgraph/api/flow.py b/trustgraph-base/trustgraph/api/flow.py index 61873e99..d0b4ca91 100644 --- a/trustgraph-base/trustgraph/api/flow.py +++ b/trustgraph-base/trustgraph/api/flow.py @@ -383,3 +383,46 @@ class FlowInstance: input ) + def objects_query( + self, query, user="trustgraph", collection="default", + variables=None, operation_name=None + ): + + # The input consists of a GraphQL query and optional variables + input = { + "query": query, + "user": user, + "collection": collection, + } + + if variables: + input["variables"] = variables + + if operation_name: + input["operation_name"] = operation_name + + response = self.request( + "service/objects", + input + ) + + # Check for system-level error + if "error" in response and response["error"]: + error_type = response["error"].get("type", "unknown") + error_message = response["error"].get("message", "Unknown error") + raise ProtocolException(f"{error_type}: {error_message}") + + # Return the GraphQL response structure + result = {} + + if "data" in response: + result["data"] = response["data"] + + if "errors" in response and response["errors"]: + result["errors"] = response["errors"] + + if "extensions" in response and response["extensions"]: + result["extensions"] = response["extensions"] + + return result + diff --git a/trustgraph-cli/pyproject.toml b/trustgraph-cli/pyproject.toml index 99e52790..feb9a306 100644 --- a/trustgraph-cli/pyproject.toml +++ b/trustgraph-cli/pyproject.toml @@ -43,6 +43,7 @@ tg-invoke-document-rag = "trustgraph.cli.invoke_document_rag:main" tg-invoke-graph-rag = "trustgraph.cli.invoke_graph_rag:main" tg-invoke-llm = "trustgraph.cli.invoke_llm:main" tg-invoke-mcp-tool = "trustgraph.cli.invoke_mcp_tool:main" +tg-invoke-objects-query = "trustgraph.cli.invoke_objects_query:main" tg-invoke-prompt = "trustgraph.cli.invoke_prompt:main" tg-load-doc-embeds = "trustgraph.cli.load_doc_embeds:main" tg-load-kg-core = "trustgraph.cli.load_kg_core:main" diff --git a/trustgraph-cli/trustgraph/cli/invoke_objects_query.py b/trustgraph-cli/trustgraph/cli/invoke_objects_query.py new file mode 100644 index 00000000..52749360 --- /dev/null +++ b/trustgraph-cli/trustgraph/cli/invoke_objects_query.py @@ -0,0 +1,124 @@ +""" +Uses the ObjectsQuery service to execute GraphQL queries against structured data +""" + +import argparse +import os +import json +import sys +from trustgraph.api import Api + +default_url = os.getenv("TRUSTGRAPH_URL", 'http://localhost:8088/') +default_user = 'trustgraph' +default_collection = 'default' + +def objects_query( + url, flow_id, query, user, collection, variables, operation_name +): + + api = Api(url).flow().id(flow_id) + + # Parse variables if provided as JSON string + parsed_variables = {} + if variables: + try: + parsed_variables = json.loads(variables) + except json.JSONDecodeError as e: + print(f"Error parsing variables JSON: {e}", file=sys.stderr) + sys.exit(1) + + resp = api.objects_query( + query=query, + user=user, + collection=collection, + variables=parsed_variables if parsed_variables else None, + operation_name=operation_name + ) + + # Check for GraphQL errors + if "errors" in resp and resp["errors"]: + print("GraphQL Errors:", file=sys.stderr) + for error in resp["errors"]: + print(f" - {error.get('message', 'Unknown error')}", file=sys.stderr) + if "path" in error and error["path"]: + print(f" Path: {error['path']}", file=sys.stderr) + # Still print data if available + if "data" in resp and resp["data"]: + print(json.dumps(resp["data"], indent=2)) + sys.exit(1) + + # Print the data + if "data" in resp: + print(json.dumps(resp["data"], indent=2)) + else: + print("No data returned", file=sys.stderr) + sys.exit(1) + +def main(): + + parser = argparse.ArgumentParser( + prog='tg-invoke-objects-query', + description=__doc__, + ) + + parser.add_argument( + '-u', '--url', + default=default_url, + help=f'API URL (default: {default_url})', + ) + + parser.add_argument( + '-f', '--flow-id', + default="default", + help=f'Flow ID (default: default)' + ) + + parser.add_argument( + '-q', '--query', + required=True, + help='GraphQL query to execute', + ) + + parser.add_argument( + '-U', '--user', + default=default_user, + help=f'User ID (default: {default_user})' + ) + + parser.add_argument( + '-C', '--collection', + default=default_collection, + help=f'Collection ID (default: {default_collection})' + ) + + parser.add_argument( + '-v', '--variables', + help='GraphQL variables as JSON string (e.g., \'{"limit": 5}\')' + ) + + parser.add_argument( + '-o', '--operation-name', + help='Operation name for multi-operation GraphQL documents' + ) + + args = parser.parse_args() + + try: + + objects_query( + url=args.url, + flow_id=args.flow_id, + query=args.query, + user=args.user, + collection=args.collection, + variables=args.variables, + operation_name=args.operation_name, + ) + + except Exception as e: + + print("Exception:", e, flush=True, file=sys.stderr) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file