def handle_opencypher_query()

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)