in src/graph_notebook/magics/graph_magic.py [0:0]
def handle_opencypher_query(self, line, cell, local_ns):
"""
This method in its own handler so that the magics %%opencypher and %%oc can both call it
"""
parser = argparse.ArgumentParser()
parser.add_argument('-pc', '--plan-cache', type=str.lower, default='auto',
help=f'Specifies the plan cache mode to use. '
f'Accepted values: {OPENCYPHER_PLAN_CACHE_MODES}')
parser.add_argument('-qt', '--query-timeout', type=int, default=None,
help=f'Neptune Analytics only. Specifies the maximum query timeout in milliseconds.')
parser.add_argument('--explain-type', type=str.lower, default='dynamic',
help=f'Explain mode to use when using the explain query mode. '
f'Accepted values: {OPENCYPHER_EXPLAIN_MODES}')
parser.add_argument('-qp', '--query-parameters', type=str, default='',
help='Parameter definitions to apply to the query. This option can accept a local variable '
'name, or a string representation of the map.')
parser.add_argument('--use-port', action='store_true', default=False,
help='Includes the port in the URI for applicable Neptune HTTP requests where it is '
'excluded by default.')
parser.add_argument('-g', '--group-by', type=str, default='~labels',
help='Property used to group nodes (e.g. code, ~id) default is ~labels')
parser.add_argument('-gd', '--group-by-depth', action='store_true', default=False,
help="Group nodes based on path hierarchy")
parser.add_argument('-gr', '--group-by-raw', action='store_true', default=False,
help="Group nodes by the raw result")
parser.add_argument('mode', nargs='?', default='query', help='query mode [query|bolt|explain]',
choices=['query', 'bolt', 'explain'])
parser.add_argument('-d', '--display-property', type=str, default='~labels',
help='Property to display the value of on each node, default is ~labels')
parser.add_argument('-de', '--edge-display-property', type=str, default='~labels',
help='Property to display the value of on each edge, default is ~type')
parser.add_argument('-t', '--tooltip-property', type=str, default='',
help='Property to display the value of on each node tooltip. If not specified, tooltip '
'will default to the node label value.')
parser.add_argument('-te', '--edge-tooltip-property', type=str, default='',
help='Property to display the value of on each edge tooltip. If not specified, tooltip '
'will default to the edge label value.')
parser.add_argument('-l', '--label-max-length', type=int, default=10,
help='Specifies max length of vertex label, in characters. Default is 10')
parser.add_argument('-rel', '--rel-label-max-length', type=int, default=10,
help='Specifies max length of edge labels, in characters. Default is 10')
parser.add_argument('--store-to', type=str, default='', help='store query result to this variable')
parser.add_argument('--store-format', type=str.lower, default='json',
help=f'Configures export type when using --store-to with base query mode. '
f'Valid inputs: {QUERY_STORE_TO_FORMATS}. Default is JSON')
parser.add_argument('--export-to', type=str, default='',
help='Export the base query mode CSV result to the provided file path.')
parser.add_argument('--ignore-groups', action='store_true', default=False, help="Ignore all grouping options")
parser.add_argument('-sp', '--stop-physics', action='store_true', default=False,
help="Disable visualization physics after the initial simulation stabilizes.")
parser.add_argument('-sd', '--simulation-duration', type=int, default=1500,
help='Specifies maximum duration of visualization physics simulation. Default is 1500ms')
parser.add_argument('--silent', action='store_true', default=False, help="Display no query output.")
parser.add_argument('-ct', '--connected-table', action='store_true', default=False,
help=f'Dynamically load jQuery and DataTables resources for iTables. For more information, see: '
f'https://mwouts.github.io/itables/quick_start.html#offline-mode-versus-connected-mode')
parser.add_argument('-r', '--results-per-page', type=int, default=10,
help='Specifies how many query results to display per page in the output. Default is 10')
parser.add_argument('--no-scroll', action='store_true', default=False,
help="Display the entire output without a scroll bar.")
parser.add_argument('--hide-index', action='store_true', default=False,
help="Hide the index column numbers when displaying the results.")
args = parser.parse_args(line.split())
logger.debug(args)
res = None
res_format = None
results_df = None
query_params = None
if args.query_parameters:
if args.query_parameters in local_ns:
query_params_input = local_ns[args.query_parameters]
else:
query_params_input = args.query_parameters
if isinstance(query_params_input, dict):
query_params = json.dumps(query_params_input)
else:
try:
query_params_dict = json.loads(query_params_input.replace("'", '"'))
query_params = json.dumps(query_params_dict)
except Exception as e:
print(f"Invalid query parameter input, ignoring.")
if args.no_scroll:
oc_layout = UNRESTRICTED_LAYOUT
oc_scrollY = True
oc_scrollCollapse = False
oc_paging = False
else:
oc_layout = DEFAULT_LAYOUT
oc_scrollY = "475px"
oc_scrollCollapse = True
oc_paging = True
if not args.silent:
tab = widgets.Tab()
titles = []
children = []
first_tab_output = widgets.Output(layout=oc_layout)
children.append(first_tab_output)
if args.mode == 'explain':
query_start = time.time() * 1000 # time.time() returns time in seconds w/high precision; x1000 to get in ms
res = self.client.opencypher_http(cell,
explain=args.explain_type,
query_params=query_params,
plan_cache=args.plan_cache,
query_timeout=args.query_timeout,
use_port=args.use_port)
query_time = time.time() * 1000 - query_start
res_replace_chars = res.content.replace(b'$', b'\$')
explain = res_replace_chars.decode("utf-8")
res.raise_for_status()
if not args.silent:
oc_metadata = build_opencypher_metadata_from_query(query_type='explain', results=None,
results_type='explain', res=res,
query_time=query_time)
titles.append('Explain')
explain_bytes = explain.encode('utf-8')
base64_str = base64.b64encode(explain_bytes).decode('utf-8')
first_tab_html = opencypher_explain_template.render(table=explain,
link=f"data:text/html;base64,{base64_str}")
elif args.mode == 'query':
if not self.client.is_analytics_domain() and args.query_timeout is not None:
print("queryTimeoutMilliseconds is not supported for Neptune DB, ignoring.")
query_start = time.time() * 1000 # time.time() returns time in seconds w/high precision; x1000 to get in ms
oc_http = self.client.opencypher_http(cell,
query_params=query_params,
plan_cache=args.plan_cache,
query_timeout=args.query_timeout,
use_port=args.use_port)
query_time = time.time() * 1000 - query_start
if oc_http.status_code == 400 and not self.client.is_analytics_domain() and args.plan_cache != "auto":
try:
oc_http_ex = json.loads(oc_http.content.decode('utf-8'))
if (oc_http_ex["code"] == "MalformedQueryException"
and oc_http_ex["detailedMessage"].startswith("Invalid input")):
print("Please ensure that you are on NeptuneDB 1.3.2.0 or later when attempting to use "
"--plan-cache.")
except:
pass
oc_http.raise_for_status()
try:
res = oc_http.json()
except JSONDecodeError:
# handle JOLT format
res_list = oc_http.text.split()
print(res_list)
res = []
for result in res_list:
result_map = json.loads(result)
if "data" in result_map:
res.append(result_map["data"])
res_format = "jolt"
if not args.silent:
oc_metadata = build_opencypher_metadata_from_query(query_type='query', results=res,
results_type=res_format, query_time=query_time)
first_tab_html = ""
results_df, has_results = oc_results_df(res, res_format)
if has_results:
titles.append('Console')
try:
gn = OCNetwork(group_by_property=args.group_by, display_property=args.display_property,
group_by_raw=args.group_by_raw,
group_by_depth=args.group_by_depth,
edge_display_property=args.edge_display_property,
tooltip_property=args.tooltip_property,
edge_tooltip_property=args.edge_tooltip_property,
label_max_length=args.label_max_length,
edge_label_max_length=args.rel_label_max_length,
ignore_groups=args.ignore_groups)
gn.add_results(res)
logger.debug(f'number of nodes is {len(gn.graph.nodes)}')
if len(gn.graph.nodes) > 0:
self.graph_notebook_vis_options['physics']['disablePhysicsAfterInitialSimulation'] \
= args.stop_physics
self.graph_notebook_vis_options['physics']['simulationDuration'] = args.simulation_duration
force_graph_output = Force(network=gn, options=self.graph_notebook_vis_options)
titles.append('Graph')
children.append(force_graph_output)
except (TypeError, ValueError) as network_creation_error:
logger.debug(f'Unable to create network from result. Skipping from result set: {res}')
logger.debug(f'Error: {network_creation_error}')
elif args.mode == 'bolt':
res_format = 'bolt'
query_start = time.time() * 1000
if query_params:
res = self.client.opencyper_bolt(cell, **query_params)
else:
res = self.client.opencyper_bolt(cell)
query_time = time.time() * 1000 - query_start
if not args.silent:
oc_metadata = build_opencypher_metadata_from_query(query_type='bolt', results=res,
results_type=res_format, query_time=query_time)
first_tab_html = ""
results_df, has_results = oc_results_df(res, res_format)
if has_results:
titles.append('Console')
# Need to eventually add code to parse and display a network for the bolt format here
if not args.silent:
if args.mode != 'explain':
# Display JSON tab
json_output = widgets.Output(layout=oc_layout)
with json_output:
print(json.dumps(res, indent=2))
children.append(json_output)
titles.append('JSON')
# Display Query Metadata Tab
metadata_output = widgets.Output(layout=oc_layout)
titles.append('Query Metadata')
children.append(metadata_output)
if first_tab_html == "" and results_df is None:
tab.children = children[1:] # the first tab is empty, remove it and proceed
else:
tab.children = children
for i in range(len(titles)):
tab.set_title(i, titles[i])
display(tab)
with metadata_output:
display(HTML(oc_metadata.to_html()))
if results_df is not None:
with first_tab_output:
visible_results, final_pagination_options, final_pagination_menu = generate_pagination_vars(
args.results_per_page)
oc_columndefs = [
{"type": "string", "targets": "_all"},
{"width": "5%", "targets": 0},
{"visible": True, "targets": 0},
{"searchable": False, "targets": 0},
{"className": "nowrap dt-left", "targets": "_all"},
{"createdCell": JavascriptFunction(index_col_js), "targets": 0},
{"createdCell": JavascriptFunction(cell_style_js), "targets": "_all", }
]
if args.hide_index:
oc_columndefs[1]["visible"] = False
init_notebook_mode(connected=args.connected_table)
show(results_df,
connected=args.connected_table,
scrollX=True,
scrollY=oc_scrollY,
columnDefs=oc_columndefs,
paging=oc_paging,
scrollCollapse=oc_scrollCollapse,
lengthMenu=[final_pagination_options, final_pagination_menu],
pageLength=visible_results,
buttons=[
"pageLength",
{
"extend": "copyHtml5",
"text": "Copy",
"exportOptions": RESULTS_EXPORT_OPTIONS
},
{
"extend": "csvHtml5",
"title": OC_RESULTS_FILENAME,
"text": "Download CSV",
"exportOptions": RESULTS_EXPORT_OPTIONS
},
{
"extend": "excelHtml5",
"filename": OC_RESULTS_FILENAME,
"title": None,
"text": "Download XLSX",
"exportOptions": RESULTS_EXPORT_OPTIONS
}
]
)
elif first_tab_html != "":
with first_tab_output:
display(HTML(first_tab_html))
if args.mode == QueryMode.EXPLAIN:
stored_results = explain
elif results_df is not None:
json_results = res
res_store_type = args.store_format
res_export_path = args.export_to
if res_store_type in PANDAS_FORMATS or res_export_path != '':
results_df = process_df_for_store(language='oc',
results_df=results_df)
stored_results = get_results_for_store(store_type=res_store_type,
pandas_results=results_df,
json_results=json_results)
export_csv_results(export_path=res_export_path,
results_df=results_df)
else:
stored_results = res
store_to_ns(args.store_to, stored_results, local_ns)