GraphAccess Reference Guide

This guide is for version 5.0-rc.6+

Note: GraphAccess was formerly named "NeoAccess"


4 classes (end users typically instantiate GraphAccess, which contains InterGraph as a base class):

GraphAccess   Source code

InterGraph (available in 2 versions, for v. 4 and v. 5 of the Neo4j database)   Source code

CypherBuilder   and   CypherUtils (helper classes)   Source code


Background Information: Using Neo4j with Python : the Open-Source Library "NeoAccess"

Tutorial 1     Tutorial 2     Tutorial 3 (Pandas import)

Unit tests (with pytest)


CONTENTS:

Class GraphAccess

Class InterGraph

Class CypherBuilder

Class CypherUtils


Class GraphAccess

    IMPORTANT : this works with various versions of the Neo4j database (and, possibly,
                other graph databases), depending on the version of the underlying InterGraph library)

    High-level class to interface with the underlying graph database from Python.

    This class is a layer above its base class "InterGraph" (which is database-specific)
        and it provides a higher-level functionality for common database operations,
        such as lookup, creation, deletion, modification, import, etc.

    It makes use of separate helper classes (NOT meant for the end user) in the file cypher_utils.py
    

RETRIEVE DATA

nameargumentsreturns
matchlabels=None, internal_id=None, key_name=None, key_value=None, properties=None, clause=None, clause_binding=None, dummy_node_name="n"CypherBuilder
        Return a "CypherBuilder" object storing all the passed specifications;
        that object is expected as argument in various other functions in this library,
        in order to identify a node or group of nodes.

        IMPORTANT:  if internal_id is provided, all other conditions are DISREGARDED;
                    otherwise, an implicit AND applies to all the specified conditions.

        Note:   NO database operation is actually performed by this function.

        [Other names explored: identify(), preserve(), define_match(), locate(), choose() or identify()]

        ALL THE ARGUMENTS ARE OPTIONAL (no arguments at all means "match everything in the database")
        :param labels:      A string (or list/tuple of strings) specifying one or more Neo4j labels.
                                (Note: blank spaces ARE allowed in the strings)
                                EXAMPLES:  "cars"
                                            ("cars", "powered vehicles")
                            Note that if multiple labels are given, then only nodes with ALL of them will be matched;
                            at present, there's no way to request an "OR" operation

        :param internal_id: An integer with the node's internal database ID.
                                If specified, it OVER-RIDES all the remaining arguments [except for the labels]

        :param key_name:    A string with the name of a node attribute; if provided, key_value must be present, too
        :param key_value:   The required value for the above key; if provided, key_name must be present, too
                                Note: no requirement for the key to be primary

        :param properties:  A (possibly-empty) dictionary of property key/values pairs, indicating a condition to match.
                                EXAMPLE: {"gender": "F", "age": 22}

        :param clause:      Either None, OR a (possibly empty) string containing a Cypher subquery,
                            OR a pair/list (string, dict) containing a Cypher subquery and the data-binding dictionary for it.
                            The Cypher subquery should refer to the node using the assigned dummy_node_name (by default, "n")
                                IMPORTANT:  in the dictionary, don't use keys of the form "n_par_i",
                                            where n is the dummy node name and i is an integer,
                                            or an Exception will be raised - those names are for internal use only
                                EXAMPLES:   "n.age < 25 AND n.income > 100000"

        :param clause_binding:  EXAMPLE {"max_weight": 100} , if the clause is "n.weight < $max_weight"

        :param dummy_node_name: A string with a name by which to refer to the node (by default, "n");
                                only used if a `clause` argument is passed

        :return:            A python data dictionary, to preserve together all the passed arguments
        
nameargumentsreturns
get_nodesmatch :int|str|CypherBuilder, return_internal_id=False, return_labels=False, order_by=None, limit=None, single_row=False, single_cell=""
        By default, it returns a list of the records (as dictionaries of ALL the key/value node properties)
        corresponding to all the Neo4j nodes specified by the given match data.
        However, if the flags "single_row" or "single_cell" are set, simpler data structures are returned

        :param match:           EITHER an integer or string with an internal database node id,
                                    OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes

        :param return_internal_id:  Flag indicating whether to also include the internal database node ID in the returned data
                                    (using "internal_id" as its key in the returned dictionary)
        :param return_labels:   Flag indicating whether to also include the database label names in the returned data
                                    (using "node_labels" as its key in the returned dictionary)

        :param order_by:        (OPTIONAL) String with the key (field) name to order by, in ascending order
                                    Caution: lower and uppercase names are treated differently in the sort order

        :param limit:           (OPTIONAL) Integer to specify the maximum number of nodes returned

        :param single_row:      Meant in situations where only 1 node (record) is expected, or perhaps one wants to sample the 1st one.
                                    If True and a record or records were found, a dict will be returned instead of a list (containing the 1st record);
                                    if nothing was found, None will be returned [to distinguish it from a found record with no fields!]

        :param single_cell:     Meant in situations where only 1 node (record) is expected, and one wants only 1 specific field of that record.
                                If single_cell is specified, return the value of the field by that name in the first node
                                Note: this will be None if there are no results, or if the first (0-th) result row lacks a key with this name

        :return:                If single_cell is specified, return the value of the field by that name in the first node.
                                If single_row is True, return a dictionary with the information of the first record (or None if no record exists)
                                Otherwise, return a (possibly-empty) list whose entries are dictionaries with each record's information
                                    (the node's attribute names are the keys)
                                    EXAMPLE: [  {"gender": "M", "age": 42, "condition_id": 3},
                                                {"gender": "M", "age": 76, "location": "Berkeley"}
                                             ]
                                    Note that ALL the attributes of each node are returned - and that they may vary across records.
                                    If the flag return_nodeid is set to True, then an extra key/value pair is included in the dictionaries,
                                            of the form     "internal_id": some integer with the Neo4j internal node ID
                                    If the flag return_labels is set to True, then an extra key/value pair is included in the dictionaries,
                                            of the form     "node_labels": [list of Neo4j label(s) attached to that node]
                                    EXAMPLE using both of the above flags:
                                        [  {"internal_id": 145, "node_labels": ["person", "client"], "gender": "M", "condition_id": 3},
                                           {"internal_id": 222, "node_labels": ["person"], "gender": "M", "location": "Berkeley"}
                                        ]
        
nameargumentsreturns
get_dfmatch: Union[int, CypherBuilder], order_by=None, limit=Nonepd.DataFrame
        Similar to get_nodes(), but with fewer arguments - and the result is returned as a Pandas dataframe

        [See get_nodes() for more information about the arguments]

        :param match:       EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param order_by:    Optional string with the key (field) name to order by, in ascending order
                                Note: lower and uppercase names are treated differently in the sort order
        :param limit:       Optional integer to specify the maximum number of nodes returned

        :return:            A Pandas dataframe
        
nameargumentsreturns
get_node_internal_idmatch :CypherBuilderint|str
        Return the internal database ID of a SINGLE node identified by the "match" data
        created by a call to match().

        If not found, or if more than 1 found, an Exception is raised

        :param match:   A "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :return:        An integer or string with the internal database ID of the located node,
                        if exactly 1 node is found; otherwise, raise an Exception
        
nameargumentsreturns
get_record_by_primary_keylabels: str, primary_key_name: str, primary_key_value, return_internal_id=FalseUnion[dict, None]
        Return the first (and it ought to be only one) record with the given primary key, and the optional label(s),
        as a dictionary of all its attributes.

        If more than one record is found, an Exception is raised.
        If no record is found, return None.

        :param labels:              A string or list/tuple of strings.  Use None if not to be included in search
        :param primary_key_name:    The name of the primary key by which to look the record up
        :param primary_key_value:   The desired value of the primary key
        :param return_internal_id:  If True, an extra entry is present in the dictionary, with the key "internal_id"

        :return:                    A dictionary, if a unique record was found; or None if not found
        
nameargumentsreturns
exists_by_keylabels: str, key_name: str, key_valuebool
        Return True if a node with the given labels and key_name/key_value exists, or False otherwise

        :param labels:      A string or list/tuple of strings
        :param key_name:    A string with the name of a node attribute
        :param key_value:   The desired value of the key_name attribute
        :return:            True if a node with the given labels and key_name/key_value exists,
                                or False otherwise
        
nameargumentsreturns
exists_by_internal_idinternal_idbool
        Return True if a node with the given internal database ID exists, or False otherwise

        :param internal_id: An integer with a node's internal database ID
        :return:            True if a node with the given internal Neo4j exists, or False otherwise
        
nameargumentsreturns
count_nodeslabels=Noneint
        Compute and return the total number of nodes in the database, optionally matching the specified labels

        :param labels:  [OPTIONAL] String, or list/tuple of strings, with the desired node label(s)
        :return:        The number of nodes in the database (matching the labels, if specified)
        
nameargumentsreturns
get_single_fieldmatch: Union[int, CypherBuilder], field_name: str, order_by=None, limit=Nonelist
        For situations where one is fetching just 1 field,
        and one desires a list of the values of that field, rather than a dictionary of records.
        In other respects, similar to the more general get_nodes()

        :param match:       EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param field_name:  A string with the name of the desired field (attribute)
        :param order_by:    see get_nodes()
        :param limit:       see get_nodes()

        :return:  A list of the values of the field_name attribute in the nodes that match the specified conditions
        
nameargumentsreturns
get_node_labelsinternal_id: int[str]
        Return a list whose elements are the label(s) of the node specified by its Neo4j internal ID

        :param internal_id: An integer with a Neo4j node id
        :return:            A list of strings with the names of all the labels of the given node
        
nameargumentsreturns
find_first_duplicatelabels :str, property_name :strUnion[dict, None]
        Search the database for node duplicates based on the given labels/property_name pairing;
        return the first duplicate, or None if not found

        :param labels:          For now, just 1 label
        :param property_name:   A string with the name of the node property of interest
        :return:                If no duplicates are present, return None;
                                otherwise return a dict such as
                                {'FIRST_INTERNAL_ID': 123, 'SECOND_INTERNAL_ID': 999, 'my_property_name': "I'm a duplicate"}
        
nameargumentsreturns
sample_propertieslabel :str, sample_size=10{str}
        Take a sample of the given size of the database nodes with the given label,
        and form a set of ALL the properties that are set on any of those nodes.

        Meant as an estimate of the properties (typically) used, in current usage of the database,
        for nodes of a given label.

        CAUTION: In a graph database, any node may freely deviate - and have, or not have, any Properties
                 it wishes.  If any type of standardization is desired, make use of the Schema layer

        :param label:       Name of the database label of interest
        :param sample_size: Number of nodes to use as a representative sampler
        :return:            Set of property (aka field) names
        

CREATE NODES

nameargumentsreturns
create_nodelabels, properties=Noneint
        Create a new node with the given label(s),
        and with the attributes/values specified in the properties dictionary.
        Return the Neo4j internal ID of the node just created.

        :param labels:      A string, or list/tuple of strings, specifying Neo4j labels (ok to have blank spaces);
                                it's acceptable to be None
        :param properties:  OPTIONAL (possibly empty or None) dictionary of properties to set for the new node.
                                EXAMPLE: {'age': 22, 'gender': 'F'}

        :return:            An integer with the internal database ID of the node just created
        
nameargumentsreturns
merge_nodelabels, properties=Nonedict
        The node gets created only if no other node with same labels and properties exists.

        Create a new node with the given label(s) and with the attributes/values specified in the properties dictionary.

        :param labels:      A string, or list/tuple of strings, specifying Neo4j labels (ok to have blank spaces)
        :param properties:  An optional (possibly empty or None) dictionary of properties
                                to try to match in an existing node, or - if not found - to set in a new node.
                                EXAMPLE: {'age': 22, 'gender': 'F'}

        :return:            A dict with 2 keys: "created" (True if a new node was created, or False otherwise)
                                                and "internal_id"
        
nameargumentsreturns
create_attached_nodelabels, properties = None, attached_to = None, rel_name = None, rel_dir = "OUT", merge=Trueint
        Create a new node (or possibly re-use an existing one),
        with the given labels and optional specified properties,
        and attached all the EXISTING nodes specified in the (possibly empty) list of nodes attached_to,
        using the given relationship name.
        All the relationships are OUTbound or INbound from the newly-created node,
        depending on the value of rel_dir.

        If merge=True, if an existing node is already present with the same labels and properties,
        it will be re-used rather than created (in that case, only the relationships will be created)

        Note: this is a simpler version of create_node_with_links()

        If any of the requested link nodes isn't found,
        then no new node is created, and an Exception is raised.

        Note: under unusual circumstances, the new node may be created even in situations where Exceptions are raised;
              for example, if attempting to create two identical relationships to the same existing node.

        EXAMPLE:
            create_attached_node(
                                    labels="COMPANY",
                                    properties={"name": "Acme Gadgets", "city": "Berkeley"},
                                    attached_to=[123, 456],
                                    rel_name="EMPLOYS"
            )

        :param labels:      Labels to assign to the newly-created node (a string, possibly empty, or list of strings)
        :param properties:  (OPTIONAL) A dictionary of optional properties to assign to the newly-created node
        :param attached_to: (OPTIONAL) An integer, or list/tuple of integers,
                                with internal database ID's to identify the existing nodes;
                                use None, or an empty list, to indicate if there aren't any
        :param rel_name:    (OPTIONAL) Name of the newly created relationships.
                                This is required, if an attached_to list was provided
        :param rel_dir:     (OPTIONAL) Either "OUT"(default), "IN" or "BOTH".  Direction(s) of the relationships to create
        :param merge:       (OPTIONAL) If True (default), a new node gets created only if there's no existing node
                                with the same properties and labels

        :return:            An integer with the internal database ID of the newly-created node
        
nameargumentsreturns
create_node_with_linkslabels :str|list|tuple, properties=None, links=None, merge=Falseint|str
        Create a new node, with the given labels and optional properties,
        and link it up to all the EXISTING nodes that are specified
        in the (possibly empty) list of link nodes, identified by their Neo4j internal ID's.

        The list of link nodes also contains the names to give to each link,
        as well as their directions (by default OUT-bound from the newly-created node)
        and, optionally, properties on the links.

        If any of the requested link nodes isn't found,
        then no new node is created, and an Exception is raised.

        Note: the new node may be created even in situations where Exceptions are raised;
              for example, if attempting to create two identical relationships to the same existing node.

        EXAMPLE (assuming the nodes with the specified internal database IDs already exist):
            create_node_with_links(
                                labels="PERSON",
                                properties={"name": "Julian", "city": "Berkeley"},
                                links=[ {"internal_id": 123, "rel_name": "LIVES IN"},
                                        {"internal_id": 456, "rel_name": "EMPLOYS", "rel_dir": "IN"},
                                        {"internal_id": 789, "rel_name": "OWNS", "rel_attrs": {"since": 2022}}
                                      ]
            )

        :param labels:      Labels to assign to the newly-created node (optional but recommended):
                                a string or list/tuple of strings; blanks allowed inside strings
        :param properties:  A dictionary of optional properties to assign to the newly-created node
        :param links:       Optional list of dicts identifying existing nodes,
                                and specifying the name, direction and optional properties
                                to give to the links connecting to them;
                                use None, or an empty list, to indicate if there aren't any.
                                Each dict contains the following keys:
                                    "internal_id"   REQUIRED - to identify an existing node
                                    "rel_name"      REQUIRED - the name to give to the link
                                    "rel_dir"       OPTIONAL (default "OUT") - either "IN" or "OUT" from the new node
                                    "rel_attrs"     OPTIONAL - A dictionary of relationship attributes
        :param merge:       (OPTIONAL; default False) If True, a new node gets created only if there's no existing node
                                with the same properties and labels

        :return:            An integer or string with the internal database ID of the newly-created node
        
nameargumentsreturns
create_node_with_relationshipslabels, properties=None, connections=Noneint
        Create a new node with relationships to zero or more PRE-EXISTING nodes
        (identified by their labels and key/value pairs).

        If the specified pre-existing nodes aren't found, then no new node is created,
        and an Exception is raised.

        On success, return the Neo4j internal ID of the new node just created.

        Note: if all connections are in one direction, and with same (property-less) relationship name,
              and to nodes with known Neo4j internal IDs, then
              the simpler method create_attached_node() may be used instead

        EXAMPLE:
            create_node_with_relationships(
                                            labels="PERSON",
                                            properties={"name": "Julian", "city": "Berkeley"},
                                            connections=[
                                                        {"labels": "DEPARTMENT",
                                                         "key": "dept_name", "value": "IT",
                                                         "rel_name": "EMPLOYS", "rel_dir": "IN"},

                                                        {"labels": ["CAR", "INVENTORY"],
                                                         "key": "vehicle_id", "value": 12345,
                                                         "rel_name": "OWNS", "rel_attrs": {"since": 2021} }
                                            ]
            )

        :param labels:      A string, or list of strings, with label(s) to assign to the new node
        :param properties:  A dictionary of properties to assign to the new node
        :param connections: A (possibly empty) list of dictionaries with the following keys
                            (all optional unless otherwise specified):
                                --- Keys to locate an existing node ---
                                    "labels"        RECOMMENDED
                                    "key"           REQUIRED
                                    "value"         REQUIRED
                                --- Keys to define a relationship to it ---
                                    "rel_name"      REQUIRED.  The name to give to the new relationship
                                    "rel_dir"       Either "OUT" or "IN", relative to the new node (by default, "OUT")
                                    "rel_attrs"     A dictionary of relationship attributes

        :return:            If successful, an integer with the Neo4j internal ID of the node just created;
                                otherwise, an Exception is raised
        

DELETE NODES

nameargumentsreturns
delete_nodesmatch: Union[int, CypherBuilder]int
        Delete the node or nodes specified by the match argument.
        Return the number of nodes deleted.

        :param match:   EITHER an integer with an internal database node id,
                            OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :return:        The number of nodes deleted (possibly zero)
        

MODIFY FIELDS

nameargumentsreturns
set_fieldsmatch: Union[int, str, CypherBuilder], set_dict: dict, drop_blanks=Trueint
        Update, possibly adding and/or dropping fields, the properties of an existing node or group of nodes.

        EXAMPLE - locate the "car" node with `vehicle id` 123 and set its color to "white" and price to 7000
            m = match(labels = "car", properties = {"vehicle id": 123})     # Object of type "CypherBuilder"
            set_fields(match=m, set_dict = {"color": "white", "price": 7000})

        NOTE: other fields are left un-disturbed

        Return the number of properties set.

        :param match:       EITHER a valid internal database ID (int or string),
                                OR a "CypherBuilder" object, as returned by match(),
                                    that contains data to identify a node or set of nodes

        :param set_dict:    A dictionary of field name/values to create/update the node's attributes
                                (note: blanks ARE allowed in the keys)
                                Blanks at the start/end of string values are zapped.
                                Any None value leads to that attribute being dropped from the nodes.
        :param drop_blanks: [OPTIONAL] If True (default), then any blank field is interpreted as a request to drop that property
                                (as opposed to setting its value to "")
                                Note that attributes set to None values are always dropped, regardless of this flag.

        :return:            The number of properties set or removed;
                                if the record wasn't found, or an empty `set_dict` was passed, return 0
                                Important: a property is counted as being "set" even if the new value is
                                           identical to the old value!
        

RELATIONSHIPS

nameargumentsreturns
get_relationship_types[str]
        Extract and return a list of all the Neo4j relationship names (i.e. types of relationships)
        present in the entire database, in no particular order

        :return:    A list of strings
        
nameargumentsreturns
add_linksmatch_from: Union[int, CypherBuilder], match_to: Union[int, CypherBuilder], rel_name:strint
        Add one or more links (aka graph edges/relationships), with the specified rel_name,
        originating in each of the nodes specified by the match_from specifications,
        and terminating in each of the nodes specified by the match_to specifications

        Return the number of links added; if none were added, or in case of error, raise an Exception.

        Notes:  - if a relationship with the same name already exists, nothing gets created (and an Exception is raised)
                - more than 1 node could be present in either of the matches

        :param match_from:  EITHER an integer or string with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param match_to:    EITHER an integer or string with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
                            Note: match_to could be the same as match_from (to add a link from a node to itself)
                            Note: match_from and match_to, if created by calls to match(),
                                  in scenarios where a clause dummy name is actually used,
                                  MUST use different clause dummy names.

        :param rel_name:    The name to give to all the new relationships between the 2 specified nodes, or sets or nodes.
                                Blanks allowed.

        :return:            The number of edges added.  If none got added, or in case of error, an Exception is raised
        
nameargumentsreturns
add_links_fastmatch_from :Union[int, str], match_to :Union[int, str], rel_name :strint
        Method optimized for speed.  Only internal database ID's are used.

        Add a links (aka graph edges/relationships), with the specified rel_name,
        originating in the node identified by match_from,
        and terminating in the node identified by match_to

        :param match_from:  An integer or string with an internal database node id
        :param match_to:    An integer or string with an internal database node id
        :param rel_name:    The name to give to the new relationship between the 2 specified nodes.  Blanks allowed

        :return:            The number of links added.  If none got added, or in case of error, an Exception is raised
        
nameargumentsreturns
remove_linksmatch_from :int|CypherBuilder, match_to :int|CypherBuilder, rel_nameint
        Remove one or more links (aka relationships/edges)
        originating in any of the nodes specified by the match_from specifications,
        and terminating in any of the nodes specified by the match_to specifications,
        optionally matching the given relationship name (will remove all edges if the name is blank or None)

        Return the number of edges removed; if none found, or in case of error, raise an Exception.

        Notes: - the nodes themselves are left untouched
               - more than 1 node could be present in either of the matches
               - the number of relationships deleted could be more than 1 even with a single "from" node and a single "to" node;
                        Neo4j allows multiple relationships with the same name between the same two nodes,
                        as long as the relationships differ in their properties

        :param match_from:  EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param match_to:    EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
                            Note: match_from and match_to, if created by calls to match(),
                                  in scenarios where a clause dummy name is actually used,
                                  MUST use different clause dummy names.

        :param rel_name:    (OPTIONAL) The name of the relationship to delete between the 2 specified nodes;
                                if None or a blank string, all relationships between those 2 nodes will get deleted.
                                Blanks allowed.

        :return:            The number of edges removed, if edges were found.
                                If none got deleted, an Exception is raised
        
nameargumentsreturns
links_existmatch_from: Union[int, CypherBuilder], match_to: Union[int, CypherBuilder], rel_name: strbool
        Return True if one or more edges (relationships) with the specified name exist in the direction
        from and to the nodes (individual nodes or set of nodes) specified in the first two arguments.
        Typically used to find whether 2 given nodes have a direct link between them.

        :param match_from:  EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param match_to:    EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
                            Note: match_from and match_to, if created by calls to match(),
                                  in scenarios where a clause dummy name is actually used,
                                  MUST use different clause dummy names.

        :param rel_name:    The name of the relationship to look for between the 2 specified nodes.
                                Blanks are allowed inside the string

        :return:            True if one or more relationships were found, or False if not
        
nameargumentsreturns
number_of_linksmatch_from: Union[int, CypherBuilder], match_to: Union[int, CypherBuilder], rel_name: strint
        Return the number of links (aka edges or relationships) with the given name
        that exist in the direction from and to the nodes (individual nodes or set of nodes)
        that are specified in the first two arguments.

        :param match_from:  EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param match_to:    EITHER an integer with an internal database node id,
                                OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
                            Note: match_from and match_to, if created by calls to match(),
                                  in scenarios where a clause dummy name is actually used,
                                  MUST use different clause dummy names.

        :param rel_name:    The name of the relationship to look for between the 2 specified nodes or groups of nodes.
                                Blanks are allowed inside the string

        :return:            The number of links (relationships) that were found
        
nameargumentsreturns
reattach_nodenode, old_attachment, new_attachment, rel_name:str, rel_name_new=NoneNone
        Sever the relationship with the given name from the given node to the node old_attachment,
        and re-create it to the node new_attachment (optionally under a different relationship name).

        Note: relationship properties, if present, will NOT be transferred

        :param node:            An integer with the internal database ID of the node to detach and reattach
        :param old_attachment:  An integer with the internal database ID of the other node currently connected to
        :param new_attachment:  An integer with the internal database ID of the new node to connect to
        :param rel_name:        Name of the old relationship name
        :param rel_name_new:    (OPTIONAL) Name of the new relationship name (by default the same as the old one)

        :return:                None.  If unsuccessful, an Exception is raised
        
nameargumentsreturns
link_nodes_by_idsnode_id1:int, node_id2:int, rel:str, rel_props = NoneNone
        Locate the pair of Neo4j nodes with the given Neo4j internal ID's.
        If they are found, add a relationship - with the name specified in the rel argument,
        and with the specified optional properties - from the 1st to 2nd node - unless already present.

        EXAMPLE:    link_nodes_by_ids(123, 88, "AVAILABLE_FROM", {'cost': 1000})

        :param node_id1:    An integer with the internal database ID to locate the 1st node
        :param node_id2:    An integer with the internal database ID to locate the 2nd node
        :param rel:         A string specifying a relationship name
        :param rel_props:   Optional dictionary with the relationship properties.
                                EXAMPLE: {'since': 2003, 'code': 'xyz'}
        :return:            None
        
nameargumentsreturns
link_nodes_on_matching_propertylabel1:str, label2:str, property1:str, rel:str, property2=NoneNone
        Locate any pair of Neo4j nodes where all of the following hold:
                            1) the first one has label1
                            2) the second one has label2
                            3) the two nodes agree in the value of property1 (if property2 is None),
                                        or in the values of property1 in the 1st node and property2 in the 2nd node
        For any such pair found, add a relationship - with the name specified in the rel argument - from the 1st to 2nd node,
        unless already present.

        This operation is akin to a "JOIN" in a relational database; in pseudo-code:
                "WHERE label1.value(property1) = label2.value(property1)"       # if property2 is None
                    or
                "WHERE label1.value(property1) = label2.value(property2)"

        :param label1:      A string against which the label of the 1st node must match
        :param label2:      A string against which the label of the 2nd node must match
        :param property1:   Name of property that must be present in the 1st node (and also in 2nd node, if property2 is None)
        :param property2:   Name of property that must be present in the 2nd node (may be None)
        :param rel:         Name to give to all relationships that get created
        :return:            None
        

FOLLOW LINKS

nameargumentsreturns
standardize_recordsetrecordset :[dict], dummy_name="n"
        Sanitize and standardize the given recordset, typically as returned by a call to query(),
        obtained from a Cypher query that returned a group of nodes (using the dummy name "n"),
        and optionally also the internal database ID (returned as "internal_id").

        The sanitizing is done by transforming any Neo4j date and datetime format to suitable python counterparts.
        The time parts get dropped, and the date is returned in the format yyyy_mm_dd

        If applicable, insert into the records the values of the internal database ID (using the key "internal_id"),
        and/or of the node labels (using the key "node_labels")

        EXAMPLES of queries that generate recordsets in the expected formats, when passed to query():
                "MATCH (n) RETURN n"
                "MATCH (n) RETURN n, id(n) AS internal_id"

        :param recordset:   A list of dict's that contain the key "n" and optionally the key "internal_id".
                                EXAMPLE: [ {"n: {"field1": 1, "field2": "x"}, "internal_id": 88, "node_labels": ["Car", "Vehicle"]},
                                           {"n": {"PatientID": 123, "DOB": neo4j.time.DateTime(2000, 01, 31, 0, 0, 0)}, "internal_id": 4},
                                           {"n: {"timestamp": neo4j.time.DateTime(2003, 7, 15, 18, 59, 35)}, "internal_id": 53},
                                         ]
        :param dummy_name:

        :return:            A list of dict's that contain all the node properties - sanitized as needed - and,
                                optionally, an extra key named "internal_id"
                                EXAMPLE:  [ {"field1": 1, "field1": "x", "internal_id": 88, "node_labels": ["Car", "Vehicle"]},
                                            {"PatientID": 123, "DOB": "2000/01/31", "internal_id": 4},
                                            {"timestamp": 123, "DOB": "2003/07/15", "internal_id": 53}
                                          ]
        
nameargumentsreturns
follow_linksmatch: Union[int, CypherBuilder], rel_name :str, rel_dir ="OUT", neighbor_labels=None, include_id=False, include_labels=False, limit=100[dict]
        From the given starting node(s), follow all the relationships that have the specified name,
        from/into neighbor nodes (optionally requiring those nodes to have the given labels),
        and return all the properties of the located neighbor nodes, optionally also including their internal database ID's.

        :param match:           EITHER an integer with an internal database node id,
                                    OR a "CypherBuilder" object, as returned by match(),
                                    with data to identify a node or set of nodes
        :param rel_name:        A string with the name of relationship to follow.
                                    (Note: any other relationships are ignored)
        :param rel_dir:         Either "OUT"(default), "IN" or "BOTH".  Direction(s) of the relationship to follow
        :param neighbor_labels: [OPTIONAL] label(s) required on the neighbor nodes; nodes that don't match are ignored.
                                    If used, provide either a string or list of strings

        :param include_id:      [OPTIONAL] If True, also return an extra field named "internal_id",
                                    with the internal database ID value; by default, False
        :param include_labels:  [OPTIONAL] If True, also return an extra field named "node_labels",
                                    with a list of the node labels; by default, False
        :param limit:           [OPTIONAL] The max number of neighbors to visit (not in any particular order);
                                    by default 100

        :return:                A list of dictionaries with all the properties of the neighbor nodes.
                                    If `include_id` is True, then each dict also contains a key named "internal_id",
                                    with the internal database ID value.
                                    Any datetime, etc, value in the database are first converted to python strings
        
nameargumentsreturns
count_linksmatch: Union[int, CypherBuilder], rel_name: str, rel_dir="OUT", neighbor_labels = Noneint
        From the given starting node(s), count all the relationships OF THE GIVEN NAME to and/or from it,
        into/from neighbor nodes (optionally having the given labels)

        :param match:           EITHER an integer with an internal database node id,
                                    OR a "CypherBuilder" object, as returned by match(), with data to identify a node or set of nodes
        :param rel_name:        A string with the name of relationship to follow.  (Note: any other relationships are ignored)
        :param rel_dir:         Either "OUT"(default), "IN" or "BOTH".  Direction(s) of the relationship to follow
        :param neighbor_labels: Optional label(s) required on the neighbors.  If present, either a string or list of strings

        :return:                The total number of inbound and/or outbound relationships to the given node(s)
        
nameargumentsreturns
get_parents_and_childreninternal_id: int()
        Fetch all the nodes connected to the given one by INbound relationships to it (its "parents"),
        as well as by OUTbound relationships to it (its "children")

        :param internal_id: An integer with a Neo4j internal node ID
        :return:            A dictionary with 2 keys: 'parent_list' and 'child_list'
                                The values are lists of dictionaries with 3 keys: "internal_id", "label", "rel"
                                EXAMPLE of individual items in either parent_list or child_list:
                                {'internal_id': 163, 'labels': ['Subject'], 'rel': 'HAS_TREATMENT'}
        
nameargumentsreturns
get_siblingsinternal_id: int, rel_name: str, rel_dir="OUT", order_by=None[int]
        Return the data of all the "sibling" nodes of the given one.
        By "sibling", we mean: "sharing a link (by default outbound) of the specified name,
        to a common other node".

        EXAMPLE: 2 nodes, "French" and "German",
                 each with a outbound link named "subcategory_of" to a third node,
                 will be considered "siblings" under rel_name="subcategory_of" and rel_dir="OUT

        :param internal_id: Integer with the internal database ID of the node of interest
        :param rel_name:    The name of the relationship used to establish a "siblings" connection
        :param rel_dir:     (OPTIONAL) Either "OUT" (default) or "IN".  The link direction that is expected from the
                                start node to its "parents" - and then IN REVERSE to the parent's children
        :param order_by:    (OPTIONAL) If specified, it must be the name of a field in
                                the sibling nodes, to order the results by; capitalization is ignored
        :return:            A list of dictionaries, with one element for each "sibling";
                                each element contains the 'internal_id' and 'node_labels' keys,
                                plus whatever attributes are stored on that node.
                                EXAMPLE of single element:
                                {'name': 'French', 'internal_id': 123, 'node_labels': ['Categories']}
        
nameargumentsreturns
get_link_summaryinternal_id, omit_names = Nonedict
        Return a dictionary structure identifying the names and counts of all
        inbound and outbound links to and from the given node.

        :param internal_id: The internal database ID of the node of interest
        :param omit_names:  [OPTIONAL] List of relationship names to disregard

        :return:            A dictionary with the names and counts of the inbound and outbound links.
                            This dictionary has two keys, "in" and "out";
                            their respective values are (possibly empty) lists.
                            Each list entry is a pair of the form (link_name, count)
                            EXAMPLE:
                                {
                                    "in": [
                                        ("served_at", 1)
                                    ],
                                    "out": [
                                        ("located_in", 1),
                                        ("cuisine_type", 2)
                                    ]
                                }
        
nameargumentsreturns
explore_neighborhoodstart_id :int|str, max_hops=2, avoid_links=None[str]
        Return nearby nodes, from a given start node, by following a max number of link,
        and optionally avoiding traversing links with some specified names.

        :param start_id:    An int or string with an internal database ID
        :param max_hops:    Integer >= 1 with the maximum number of links to follow in the graph traversal
        :param avoid_links: Name, or list/tuple of names, of links to avoid in the graph traversal
        :return:            A (possibly empty) list of dict's, with the properties of all the located nodes,
                                plus the 2 special keys "internal_id" and "node_labels".
                                The start node is NOT included.
        

READ IN PANDAS

nameargumentsreturns
load_pandasdf :Union[pd.DataFrame, pd.Series], labels :Union[str, List[str], Tuple[str]], merge_primary_key=None, merge_overwrite=False, rename=None, ignore_nan=True, max_chunk_size=10000[int]
        Load a Pandas Data Frame (or Series) into Neo4j.
        Each row is loaded as a separate node.

        Columns whose dtype is integer will appear as integer data types in the Neo4j nodes
            (however, if a NaN is present in a Pandas column, it'll automatically be transformed to float)

        Any Pandas' datetime values get converted to Neo4j's format.

        Database indexes are added as needed, if the "merge_primary_key" argument is used.

        :param df:              A Pandas Data Frame (or Series) to import into Neo4j.
                                    If it's a Series, it's treated as a single column (named "value", if it lacks a name)

        :param labels:          A string, or list/tuple of strings - representing one or more Neo4j labels
                                    to use on all the newly-created nodes

        :param merge_primary_key: (OPTIONAL) A string with the name of the field that serves as a primary key
                                    If provided, new records will be merged (rather than added) if they already exist,
                                    as determined by that primary key

        :param merge_overwrite: (OPTIONAL) Only applicable if "merge_primary_key" is set.
                                    If True then on merge the existing nodes will be completely overwritten with the new data,
                                    otherwise they will be updated with new information (keys that are not present
                                    in the df argument will be left unaltered)

        :param rename:          Optional dictionary to rename the Pandas dataframe's columns to
                                    EXAMPLE {"current_name": "name_we_want"}
        :param ignore_nan       If True, node properties created from columns of dtype float
                                    will only be set if they are not NaN.
                                    (Note: the moment a NaN is present, columns of integers in a dataframe
                                           will automatically become floats)
        :param max_chunk_size:  To limit the number of Pandas rows loaded into the database at one time

        :return:                A (possibly-empty) list of the internal database ID's of the created nodes
        
nameargumentsreturns
pd_datetime_to_neo4j_datetimedf: pd.DataFramepd.DataFrame
        If any column in the given Pandas DataFrame is of dtype datetime or timedelta,
        clone the Data Frame and replace its entries with a Neo4j-friendly datetime type.

        If any change is needed, return a modified COPY of the dataframe;
        otherwise, return the original dataframe (no cloning done)

        Note: one must be vigilant against STRING columns with values that look like datetimes,
              but are not of that type!

        EXAMPLE: an entry such as pd.Timestamp('2023-01-01 00:00:00')
                 will become an object neo4j.time.DateTime(2023, 1, 1, 0, 0, 0, 0)

        :param df:  A Pandas data frame
        :return:    Either the same data frame, or a modified version of a clone of it
        

JSON IMPORT EXPORT

nameargumentsreturns
export_dbase_jsondict
        Export the entire Neo4j database as a JSON string.

        IMPORTANT: APOC must be activated in the database, to use this function.
                   Otherwise it'll raise an Exception.
                   Different versions of the Neo4j 4.4 database return different values for the dicts with type "relationship" !!!
                        In the dicts for the "start" and "end" nodes, sometime the "properties" are included, and sometimes not!!!

        EXAMPLE:
        { 'nodes': 2,
          'relationships': 1,
          'properties': 6,
          'data': '[{"type":"node","id":"3","labels":["User"],"properties":{"name":"Adam","age":32,"male":true}},\n
                    {"type":"node","id":"4","labels":["User"],"properties":{"name":"Eve","age":18}},\n
                    {"type":"relationship","id":"1","label":"KNOWS","properties":{"since":2003},"start":{"id":"3","labels":["User"],"properties":{"name":"Eve"}},"end": {"id":"4","labels":["User"],"properties":{"name":"Adam","age":30}}}\n
                   ]'
        }

        SIDE NOTE: the Neo4j *Browser* uses a slightly different format for NODES:
                {
                  "identity": 4,
                  "labels": [
                    "User"
                  ],
                  "properties": {
                    "name": "Eve",
                    "age": 18
                  }
                }
              and a substantially more different format for RELATIONSHIPS:
                {
                  "identity": 1,
                  "start": 3,
                  "end": 4,
                  "type": "KNOWS",
                  "properties": {
                    "since": 2003
                  }
                }

        :return:    A dictionary with the following entries:
                        "nodes":            the number of nodes exported
                        "relationships":    the number of relationships exported
                        "properties":       the number of properties exported
                        "data":             the actual export as a JSON string that encodes a list of dict's
        
nameargumentsreturns
export_nodes_rels_jsonnodes_query="", rels_query=""{}
        Export the specified nodes, plus the specified relationships, as a JSON string.
        The default empty strings are taken to mean (respectively) ALL nodes/relationships.

        For details on the formats, see export_dbase_json()

        IMPORTANT:  APOC must be activated in the database for this function.
                    Otherwise it'll raise an Exception

        :param nodes_query: A Cypher query to identify the desired nodes (exclusive of RETURN statements)
                                    The dummy variable for the nodes must be "n"
                                    Use "" to request all nodes
                                    EXAMPLE: "MATCH (n) WHERE (n:CLASS OR n:PROPERTY)"
        :param rels_query:   A Cypher query to identify the desired relationships (exclusive of RETURN statements)
                                    The dummy variable for the relationships must be "r"
                                    Use "" to request all relationships (whether or not their end nodes are also exported)
                                    EXAMPLE: "MATCH ()-[r:HAS_PROPERTY]->()"

        :return:    A dictionary specifying the number of nodes exported,
                    the number of relationships, and the number of properties,
                    as well as a "data" field with the actual export as a JSON string

        
nameargumentsreturns
is_literalvaluebool
        Return True if the given value represents a literal (in terms of database storage)

        :param value:
        :return:
        
nameargumentsreturns
import_jsonjson_str: str, root_labels="import_root_label", parse_only=False, provenance=NoneList[int]
        Import the data specified by a JSON string into the database.

        CAUTION: A "postorder" approach is followed: create subtrees first (with recursive calls), then create root last;
        as a consequence, in case of failure mid-import, there's no top root, and there could be several fragments.
        A partial import might need to be manually deleted.

        :param json_str:    A JSON string representing the data to import
        :param root_labels: String, or list of strings, to be used as Neo4j labels for the root node(s)
        :param parse_only:  If True, the parsed data will NOT be added to the database
        :param provenance:  Optional string to store in a "source" attribute in the root node
                                (only used if the top-level JSON structure is an object, i.e. if there's a single root node)

        :return:            List of integer ID's (possibly empty), of the root node(s) created
        
nameargumentsreturns
create_nodes_from_python_datapython_data, root_labels: Union[str, List[str]], level=1List[int]
        Recursive function to add data from a JSON structure to the database, to create a tree:
        either a single node, or a root node with children.
        A "postorder" approach is followed: create subtrees first (with recursive calls), then create root last.

        If the data is a literal, first turn it into a dictionary using a key named "value".

        Return the Neo4j ID's of the root node(s)

        :param python_data: Python data to import.
                                The data can be a literal, or list, or dictionary
                                - and lists/dictionaries may be nested
        :param root_labels: String, or list of strings, to be used as Neo4j labels for the root node(s)
        :param level:       Recursion level (also used for debugging, to make the indentation more readable)
        :return:            List of integer Neo4j internal ID's (possibly empty), of the root node(s) created
        
nameargumentsreturns
dict_importerd: dict, labels, level: intint
        Import data from a Python dictionary.
        If the data is nested, it uses a recursive call to create_nodes_from_python_data()

        :param d:       A Python dictionary with data to import
        :param labels:  String, or list of strings, to be used as Neo4j labels for the node
        :param level:   Integer with recursion level (used just to format debugging output)
        :return:        Integer with the internal database id of the newly-created (top-level) node
        
nameargumentsreturns
list_importerl: list, labels, level[int]
        Import data from a list.
        If the data is nested, it uses a recursive call to create_nodes_from_python_data()

        :param l:       A list with data to import
        :param labels:  String, or list of strings, to be used as Neo4j labels for the node
        :param level:   Integer with recursion level (just used to format debugging output)
        :return:        List (possibly empty) of internal database id's of the newly-created nodes
        
nameargumentsreturns
import_json_dumpjson_str: str, extended_validation = Truestr
        Used to import data from a database dump that was done with export_dbase_json() or export_nodes_rels_json().

        Import nodes and relationships into the database, as specified in the JSON code
        that was created by the earlier data dump.

        IMPORTANT: the internal id's of the nodes need to be shifted,
              because one cannot force the Neo4j internal id's to be any particular value...
              and, besides (if one is importing into an existing database), particular id's may already be taken.

        :param json_str:            A JSON string with the format specified under export_dbase_json()
        :param extended_validation: If True, an attempt is made to try to avoid partial imports,
                                        by running extended validations prior to importing
                                        (it will make a first pass thru the data, and hence take longer)

        :return:                    A status message with import details if successful;
                                        or raise an Exception if not.
                                        If an error does occur during import then the import is aborted -
                                        and the number of imported nodes & relationships is returned in the Exception raised.
        

VISUALIZATION

nameargumentsreturns
node_tabular_displaynode_list :list, fields=None, dummy_name=None, limit=15pd.DataFrame|None
        Tabular display of the requested fields from the given list of nodes data,
        typically as returned from get_nodes()
        or from the queries such as "MATCH (x:Person) RETURN x, id(x) AS internal_id"

        Node labels and their internal ID's are included in the table *if* they are part of the passed data

        :param node_list:   A list whose elements represent data from nodes;
                                the following EXAMPLES show the different formats can be used for the list elements:

                                {"field_1": 3, "field_2": "hello"}                          # Simple dict of node properties
                                {"field_1": 3, "field_2": "hello",
                                    "internal_id": 123, "node_labels": ["Car", "Vehicle"]}  # Optionally include "internal_id" and/or "node_labels"
                                { "n":  {"field_1": 3, "field_2": "hello"} }                # Outer dict with dummy name
                                { "internal_id": 123, "node_labels": ["Car", "Vehicle"] ,
                                        "n":  {"field_1": 3, "field_2": "hello"} }          # Optionally include "internal_id" and/or "node_labels" in outer list


        :param fields:      A string, or list/tuple of strings, with the name(s) of the desired field(s) to include.
                                If None, ALL fields are included (plus the node labels and internal ID's, if present);
                                if the fields don't match across all records, NaN's will be inserted into the dataframe
        :param dummy_name:  A string to identify where the node data resides in the elements of `node_list`, if not at top level
        :param limit:       Max number of records (nodes) to include

        :return:            If the list of node is empty, None is returned;
                                otherwise, a Panda's DataFrame with a tabular view of the specified fields (properties),
                                with columns in the following order: "node_labels" (if present), all the fields in the order of
                                the `fields` list, "internal_id" (if present)
                                Note: "internal_id" might not show up at the far right if `fields` is None, and different records
                                      have variable field lists
        

DEBUGGING SUPPORT

nameargumentsreturns
block_query_executionNone
        Activate a special testing-only only modality.
        All the Cypher queries will get printed (just like done in debug mode),
        but no database operations will actually be performed.
        This modality will remain in effect until a call is made to unblock_query_execution()

        Caution: many functions will fail validations on the results
                 of the query that wasn't executed.  This modality should probably
                 be combined with an Exception catch

        :return: None
        
nameargumentsreturns
unblock_query_executionNone
        Terminate the special "BLOCK QUERY EXECUTION" test mode, entered by a call to block_query_execution().
        From now on, Cypher queries will be normally executed.
        The state of the "debug" mode (only affecting extra diagnostic printing) won't be affected.

        :return:    None
        
nameargumentsreturns
assert_valid_internal_idinternal_id: intNone
        Raise an Exception if the argument is not a valid database internal ID

        :param internal_id: Alleged Neo4j internal database ID
        :return:            None
        
nameargumentsreturns
debug_printinfo: str, trim=FalseNone
        If the class property "debug" is set to True,
        print out the passed info string,
        optionally trimming it, if too long

        :param info:
        :param trim:
        :return:        None
        
nameargumentsreturns
debug_trimdata, max_len = 150str
        Abridge the given data (first turning it into a string if needed), if excessively long,
        using ellipses " ..." for the omitted data.
        Return the abridged data.

        :param data:    String with data to possibly abridge
        :param max_len: Max number of characters to show from the data argument
        :return:        The (possibly) abridged text
        
nameargumentsreturns
debug_trim_printdata, max_len = 150None
        Abridge the given data (first turning it into a string if needed),
        if it is excessively long; then print it

        :param data:    String with data to possibly abridge, and then print
        :param max_len: Max number of characters to show from the data argument
        :return:        None
        
nameargumentsreturns
indent_chooserlevel: intstr
        Create an indent based on a "level": handy for debugging recursive functions

        :param level:
        :return:
        



Class InterGraph

    IMPORTANT : this is the DATABASE-SPECIFIC bottom layer of the "BrainAnnex" library.
                Currently, there's a version of InterGraph for Neo4j v4 and one for Neo4j v5
                (both versions have the same class name, and present the same methods)

    A thin wrapper around the Neo4j python connectivity library "Neo4j Python Driver"

    This is a bottom layer that is dependent on the specific graph database
    (for operations such as connection, indexes, constraints),
    and insulates the higher layers from it.

    This "CORE" library allows the execution of arbitrary Cypher (query language) commands,
    and helps manage the complex data structures that they return.
    It may be used independently,
    or as the foundation of the higher-level child class, "GraphAccess"
    
nameargumentsreturns
__init__host=os.getenv("NEO4J_HOST"), credentials=(os.getenv("NEO4J_USER"), os.getenv("NEO4J_PASSWORD")), apoc=False, debug=False, autoconnect=True
        If unable to create a Neo4j driver object, raise an Exception
        reminding the user to check whether the Neo4j database is running

        :param host:        URL to connect to database with.
                                EXAMPLES: bolt://123.456.0.29:7687  ,  bolt://your_domain.com:7687  ,  neo4j://localhost:7687
                                DEFAULT: read from NEO4J_HOST environmental variable
        :param credentials: Pair of strings (tuple or list) containing, respectively, the database username and password
                                DEFAULT: read from NEO4J_USER and NEO4J_PASSWORD environmental variables
        :param apoc:        Flag indicating whether apoc library is used on Neo4j database to connect to
                                Notes: APOC, if used, must also be enabled on the database.
                                The only method currently requiring APOC is export_dbase_json()
        :param debug:       Flag indicating whether a debug mode is to be used :
                                if True, all the Cypher queries, and some additional info, will get printed
        :param autoconnect  Flag indicating whether the class should establish connection to database at initialization
        
nameargumentsreturns
connectNone
        Attempt to establish a connection to the Neo4j database, using the credentials stored in the object.
        In the process, create and save a driver object.
        
nameargumentsreturns
test_dbase_connectionNone
        Attempt to perform a trivial Neo4j query, for the purpose of validating
        whether a connection to the database is possible.
        A failure at start time is typically indicative of invalid credentials

        :return:    None
        
nameargumentsreturns
versionstr
        Return the version of the Neo4j driver being used.  EXAMPLE: "4.4.12"

        :return:    A string with the version number
        
nameargumentsreturns
closeNone
        Terminate the database connection.
        Note: this method is automatically invoked
              after the last operation included in "with" statements

        :return:    None
        

RUN GENERIC QUERIES

nameargumentsreturns
queryq: str, data_binding=None, single_row=False, single_cell="", single_column=""
        Run a Cypher query.  Best suited for Cypher queries that return individual values,
        but may also be used with queries that return nodes or relationships or paths - or nothing.

        Execute the query and fetch the returned values as a list of dictionaries.
        In cases of no results, return an empty list.
        A new session to the database driver is started, and then immediately terminated after running the query.

        ALTERNATIVES:
            * if the Cypher query returns nodes, and one wants to extract the internal Neo4j ID's or labels
              (in addition to all the properties and their values) then use query_extended() instead.

            * in case of queries that alter the database (and may or may not return values),
              use update_query() instead, in order to retrieve information about the effects of the operation

        :param q:               A string with a Cypher query
        :param data_binding:    An optional Cypher dictionary
                                EXAMPLE, assuming that the cypher string contains the substrings "$node_id":
                                        {'node_id': 20}

        :param single_row:      (OPTIONAL) If True, return a dictionary containing just the first (0-th) result row, if present,
                                    or None in case of no results.
                                    Note that if the query returns multiple records, the picked one
                                    will be arbitrary, unless an ORDER BY is included in the query
                                    EXAMPLES of returned values:
                                        {"brand": "Toyota", "color": "White"}
                                        {'n': {}}

        :param single_cell:     (OPTIONAL) Meant for situations where only 1 node (record) is expected,
                                    and one wants only 1 specific field of that record.
                                    If a string is provided, return the value of the field by that name
                                    in the first (0-th) retrieved record.
                                    In case of no nodes found, or a node that lacks a key with this name, None will be returned.
                                    Note that if the query returns multiple records, the picked one
                                    will be arbitrary, unless an ORDER BY is included in the query

        :param single_column:   (OPTIONAL) Name of the column of interest.
                                If provided, assemble a list (possibly empty)
                                from all the values of that particular column all records.
                                Note: can also be used to extract data from a particular node, for queries that return whole nodes

        :return:        If any of single_row, single_cell or single_column are True, see info under their entries.
                        If those arguments are all False, it returns a (possibly empty) list of dictionaries.
                        Each dictionary in the list will depend on the nature of the Cypher query.
                        EXAMPLES:
                            Cypher returns nodes (after finding or creating them): RETURN n1, n2
                                    -> list item such as {'n1': {'gender': 'M', 'patient_id': 123}
                                                          'n2': {'gender': 'F', 'patient_id': 444}}
                            Cypher returns attribute values that get renamed: RETURN n.gender AS client_gender, n.pid AS client_id
                                    -> list items such as {'client_gender': 'M', 'client_id': 123}
                            Cypher returns attribute values without renaming them: RETURN n.gender, n.pid
                                    -> list items such as {'n.gender': 'M', 'n.pid': 123}
                            Cypher returns a single computed value
                                    -> a single list item such as {"count(n)": 100}
                            Cypher returns RELATIONSHIPS (LINKS), with or without properties: MERGE (c)-[r :PAID_BY]->(p)
                                    -> a single list item such as [{ 'r': ({}, 'PAID_BY', {}) }]   NOTE: link properties are NOT returned
                            Cypher returns a path:   MATCH p= .......   RETURN p
                                    -> list item such as {'p': [ {'name': 'Eve'}, 'LOVES', {'name': 'Adam'} ] }
                            Cypher creates nodes (without returning them)
                                    -> empty list
        
nameargumentsreturns
query_extendedq: str, data_binding = None, flatten = False, fields_to_exclude = None[dict]
        Extended version of query(), meant to extract additional info
        for queries that return so-called Graph Data Types,
        i.e. nodes, relationships or paths,
        such as:    "MATCH (n) RETURN n"
                    "MATCH (n1)-[r]->(n2) RETURN r"

        For example, useful in scenarios where nodes were returned,
        and their internal database IDs and/or labels are desired
        (in addition to all the properties and their values)

        Unless the flatten flag is True, individual records are kept as separate lists.
            For example, "MATCH (b:boat), (c:car) RETURN b, c"
            will return a structure such as [ [b1, c1] , [b2, c2] ]  if flatten is False,
            vs.  [b1, c1, b2, c2]  if  flatten is True.  (Note: each b1, c1, etc, is a dictionary.)

        :param q:                   A Cypher query : typically, one returning nodes, relationships or paths
        :param data_binding:        An optional Cypher dictionary
                                    EXAMPLE, assuming that the cypher string contains the substring "$age":
                                            {'age': 20}
        :param flatten:             Flag indicating whether the Graph Data Types need to remain clustered by record,
                                        or all placed in a single flattened list
        :param fields_to_exclude:   [OPTIONAL] list of strings with name of fields
                                        (in the database, or special keys added by this function)
                                        that wishes to drop.  No harm in listing fields that aren't present.
                                        For example, if the query returns relationships, there will be 3 keys
                                        named 'neo4j_start_node', 'neo4j_end_node', 'neo4j_type' ('type' is the name of the relationship)

        :return:        A (possibly empty) list of dictionaries, if flatten is True,
                        or a list of list, if flatten is False.
                        Each item in the lists is a dictionary, with details that will depend on which Graph Data Types
                                were returned in the Cypher query.

                        EXAMPLE with flatten=True for a query returning nodes "MATCH n RETURN n":
                                [   {'year': 2023, 'make': 'Ford', 'internal_id': 123, 'node_labels': ['Motor Vehicle']},
                                    {'year': 2013, 'make': 'Toyota', 'internal_id': 4, 'node_labels': ['Motor Vehicle']}
                                ]
                        EXAMPLE with flatten=False for that same query returning nodes "MATCH n RETURN n":
                                [   [{'year': 2023, 'make': 'Ford', 'internal_id': 123, 'node_labels': ['Motor Vehicle']}],
                                    [{'year': 2013, 'make': 'Toyota', 'internal_id': 4, 'node_labels': ['Motor Vehicle']}]
                                ]

                        EXAMPLE of *individual items* - for a returned NODE
                            {'gender': 'M', 'age': 20, 'internal_id': 123, 'node_labels': ['patient']}

                        EXAMPLE of *individual items* - for a returned RELATIONSHIP (note that 'neo4j_type' is the link name,
                            and that any properties of the relationships, such as 'price', appear as key/values in the dict)
                            {'price': 7500, 'internal_id': 2,
                             'neo4j_start_node': ,
                             'neo4j_end_node': ,
                             'neo4j_type': 'bought_by'}]
        
nameargumentsreturns
update_queryq: str, data_binding=Nonedict
        Run a Cypher query and return statistics about its actions (such number of nodes created, etc.)
        Typical use is for queries that update the database.
        If the query returns any values, they are made available as  list, as the value of the key 'returned_data'.

        Note: if the query creates nodes and one wishes to obtain their internal database ID's,
              one can include Cypher code such as "RETURN id(n) AS internal_id" (where n is the dummy name of the newly-created node)

        EXAMPLE:  result = update_query("CREATE(n :CITY {name: 'San Francisco'}) RETURN id(n) AS internal_id")

                  result will be {'nodes_created': 1, 'properties_set': 1, 'labels_added': 1,
                                  'returned_data': [{'internal_id': 123}]
                                 } , assuming 123 is the internal database ID of the newly-created node

        :param q:           Any Cypher query, but typically one that doesn't return anything
        :param data_binding: Data-binding dictionary for the Cypher query

        :return:            A dictionary of statistics (counters) about the query just run
                            EXAMPLES -
                                {}      The query had no effect
                                {'nodes_deleted': 3}    The query resulted in the deletion of 3 nodes
                                {'properties_set': 2}   The query had the effect of setting 2 properties
                                {'relationships_created': 1}    One new relationship got created
                                {'returned_data': [{'internal_id': 123}]}  'returned_data' contains the results of the query,
                                                                        if it returns anything, as a list of dictionaries
                                                                        - akin to the value returned by query()
                                {'returned_data': []}  Gets returned by SET QUERIES with no return statement
                            OTHER KEYS include:
                                nodes_created, nodes_deleted, relationships_created, relationships_deleted,
                                properties_set, labels_added, labels_removed,
                                indexes_added, indexes_removed, constraints_added, constraints_removed
                                More info:  https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.SummaryCounters
        
nameargumentsreturns
empty_dbasekeep_labels=None, drop_indexes=False, drop_constraints=FalseNone
        Use this to get rid of everything in the database,
        including all the indexes and constraints (unless otherwise specified.)
        Optionally, keep nodes with a given label, or keep the indexes, or keep the constraints

        :param keep_labels:     An optional list of strings, indicating specific labels to KEEP
        :param drop_indexes:    Flag indicating whether to also ditch all indexes (by default, True)
        :param drop_constraints:Flag indicating whether to also ditch all constraints (by default, True)

        :return:                None
        

LABELS

nameargumentsreturns
get_labels[str]
        Extract and return a list of ALL the Neo4j labels present in the database.
        No particular order should be expected.
        Note: to get the labels of a particular node, use get_node_labels()

        :return:    A list of strings
        
nameargumentsreturns
delete_nodes_by_labeldelete_labels=None, keep_labels=NoneNone
        Empty out (by default completely) the Neo4j database.
        Optionally, only delete nodes with the specified labels, or only keep nodes with the given labels.
        Note: the keep_labels list has higher priority; if a label occurs in both lists, it will be kept.
        IMPORTANT: it does NOT clear indexes; "ghost" labels may remain!

        :param delete_labels:   An optional string, or list of strings, indicating specific labels to DELETE
        :param keep_labels:     An optional string or list of strings, indicating specific labels to KEEP
                                    (keep_labels has higher priority over delete_labels)
        :return:                None
        
nameargumentsreturns
bulk_delete_by_labellabel: str
        IMPORTANT: APOC required

        Meant for large databases, where the straightforward deletion operations may result
        in very large number of nodes, and take a long time (or possibly fail)

        "If you need to delete some large number of objects from the graph,
        one needs to be mindful of the not building up such a large single transaction
        such that a Java OUT OF HEAP Error will be encountered."
        See:  https://neo4j.com/developer/kb/large-delete-transaction-best-practices-in-neo4j/

        :param label:   A string with the label of the nodes to delete (blank spaces in name are ok)
        :return:        A dict with the keys "batches" and "total"
        
nameargumentsreturns
get_label_propertieslabel:strlist
        Extract and return all the property (key) names used in nodes with the given label,
        sorted alphabetically

        :param label:   A string with the name of a node label
        :return:        A list of property names, sorted alphabetically
        

INDEXES

nameargumentsreturns
get_indexespd.DataFrame
        Return all the database indexes, and some of their attributes,
        as a Pandas dataframe.

        Standard (always present) column names: "name", "labelsOrTypes", "properties".
        Other columns will depend on the (version of the) underlying graph database.

        EXAMPLE of a returned dataframe::
                 labelsOrTypes              name          properties    type  uniqueness
             0    ["my_label"]  "index_23b59623"     ["my_property"]   BTREE   NONUNIQUE
             1    ["L"]            "L.client_id"       ["client_id"]   BTREE      UNIQUE

        :return:        A (possibly-empty) Pandas dataframe
        
nameargumentsreturns
create_indexlabel :str, key :strbool
        Create a new database index, unless it already exists,
        to be applied to the pairing of the specified label and key (property).
        The standard name automatically given to the new index is of the form label.key
        EXAMPLE - to index nodes labeled "car" by their key "color", use:
                        create_index(label="car", key="color")
                  This new index - if not already in existence - will be named "car.color"

        If an existing index entry contains a list of labels (or types) such as ["l1", "l2"] ,
        and a list of properties such as ["p1", "p2"] ,
        then the given pair (label, key) is checked against ("l1_l2", "p1_p2"), to decide whether it already exists.

        :param label:   A string with the node label to which the index is to be applied
        :param key:     A string with the key (property) name to which the index is to be applied
        :return:        True if a new index was created, or False otherwise
        
nameargumentsreturns
drop_indexname: strbool
        Get rid of the index with the given name

        :param name:    Name of the index to jettison
        :return:        True if successful or False otherwise (for example, if the index doesn't exist)
        
nameargumentsreturns
drop_all_indexesincluding_constraints=TrueNone
        Eliminate all the indexes in the database and, optionally, also get rid of all constraints

        :param including_constraints:   Flag indicating whether to also ditch all the constraints
        :return:                        None
        

CONSTRAINTS

nameargumentsreturns
get_constraintspd.DataFrame
        Return all the database constraints, and some of their attributes,
        as a Pandas dataframe with 3 columns:
            name        EXAMPLE: "my_constraint"
            description EXAMPLE: "CONSTRAINT ON ( patient:patient ) ASSERT (patient.patient_id) IS UNIQUE"
            details     EXAMPLE: "Constraint( id=3, name='my_constraint', type='UNIQUENESS',
                                  schema=(:patient {patient_id}), ownedIndex=12 )"

        :return:  A (possibly-empty) Pandas dataframe with 3 columns: 'name', 'description', 'details'
        
nameargumentsreturns
create_constraintlabel :str, key :str, name=Nonebool
        Create a uniqueness constraint for a node property in the graph,
        unless a constraint with the standard name of the form `{label}.{key}.UNIQUE` is already present
        Note: it also creates an index, and cannot be applied if an index already exists.
        EXAMPLE: create_constraint("patient", "patient_id")

        :param label:   A string with the node label to which the constraint is to be applied
        :param key:     A string with the key (property) name to which the constraint is to be applied
        :param name:    Optional name to give to the new constraint; if not provided, a
                            standard name of the form `{label}.{key}.UNIQUE` is used.  EXAMPLE: "patient.patient_id.UNIQUE"
        :return:        True if a new constraint was created, or False otherwise
        
nameargumentsreturns
drop_constraintname: strbool
        Eliminate the constraint with the specified name

        :param name:    Name of the constraint to eliminate
        :return:        True if successful or False otherwise (for example, if the constraint doesn't exist)
        
nameargumentsreturns
drop_all_constraintsNone
        Eliminate all the constraints in the database

        :return:    None
        

DATA TYPES

nameargumentsreturns
property_data_type_cypherstr
        Return the Cypher fragment to be used as the name of the function
        to use to extract the data type from a node property.

        EXAMPLE:    cyp = property_data_type_cypher()
                    q = f"MATCH (n) WHERE id(n) = 123 RETURN {cyp}(n.`my_field`) AS dtype"
                    Then run the above query q

        :return:    A string with a Cypher fragment
        

DEBUGGING SUPPORT

nameargumentsreturns
debug_print_queryq :str, data_binding=None, method=NoneNone
        Print out the given Cypher query
        (and, optionally, its data binding and/or the name of the calling method)

        :param q:               String with Cypher query
        :param data_binding:    OPTIONAL dictionary
        :param method:          OPTIONAL string with the name of the calling method
                                    EXAMPLE:  "foo"
        :return:                None
        
nameargumentsreturns
debug_query_printq :str, data_binding=None, method=NoneNone
        Synonym for debug_print_query()

        Print out the given Cypher query
        (and, optionally, its data binding and/or the name of the calling method)

        :param q:               String with Cypher query
        :param data_binding:    OPTIONAL dictionary
        :param method:          OPTIONAL string with the name of the calling method
                                    EXAMPLE:  "foo"
        :return:                None
        



Class CypherBuilder

    Used to automatically assemble various parts of a Cypher query
    to be used to locate a node, or group of nodes, based on various match criteria.
    (No support for scenarios involving links.)
    
    Objects of this class (sometimes referred to as a "match structures")
    are used to facilitate a user to specify a node in a wide variety of ways - and
    save those specifications, to use as needed in later building Cypher queries.

    NO extra database operations are involved.

    IMPORTANT:  By our convention -
                    if internal_id is provided, all other conditions are DISREGARDED;
                    if it's missing, an implicit AND operation applies to all the specified conditions
                    (Regardless, all the passed data is stored in this object)


    Upon instantiation, two broad actions take place:

    First, validation and storage of all the passed specifications (the "RAW match structure"),
    that are used to identify a node or group of nodes.

    Then the generation and storage of values for the following 6 properties:

        1) "node":  A string, defining a node in a Cypher query, incl. parentheses but *excluding* the "MATCH" keyword
        2) "where": A string, defining the "WHERE" part of the subquery (*excluding* the "WHERE"), if applicable;
                    otherwise, a blank
        3) "clause_binding"     A dict meant to provide the data for a clause
        4) "data_binding":      A (possibly empty) data-binding dictionary
        5) "dummy_node_name":   A string used for the node name inside the Cypher query (by default, "n");
                                potentially relevant to the "node" and "where" values
        6) "cypher":            The complete Cypher query, exclusive of RETURN statement and later parts;
                                the WHERE pass will be missing if there are no clauses

        EXAMPLES:
            *   node: "(n)"
                    where: ""
                    clause_binding: {}
                    data_binding: {}
                    dummy_node_name: "n"
                    cypher: "MATCH (n)"
            *   node: "(p :`person` )"
                    where: ""
                    clause_binding: {}
                    data_binding: {}
                    dummy_node_name: "p"
            *   node: "(n  )"
                    where: "id(n) = 123"
                    clause_binding: {}
                    data_binding: {}
                    dummy_node_name: "n"
            *   node: "(n :`car`:`surplus inventory` )"
                    where: ""
                    clause_binding: {}
                    data_binding: {}
                    dummy_node_name: "n"
            *    node: "(n :`person` {`gender`: $n_par_1, `age`: $n_par_2})"
                    where: ""
                    clause_binding: {}
                    data_binding: {"n_par_1": "F", "n_par_2": 22}
                    dummy_node_name: "n"
            *   node: "(n :`person` {`gender`: $n_par_1, `age`: $n_par_2})"
                    where: "n.income > 90000 OR n.state = 'CA'"
                    clause_binding: {}
                    data_binding: {"n_par_1": "F", "n_par_2": 22}
                    dummy_node_name: "n"
            *   node: "(n :`person` {`gender`: $n_par_1, `age`: $n_par_2})"
                    where: "n.income > $min_income"
                    clause_binding:  {"$min_income": 90000}
                    data_binding: {"n_par_1": "F", "n_par_2": 22, "min_income": 90000}
                    dummy_node_name: "n"
    
nameargumentsreturns
__init__internal_id=None, labels=None, key_name=None, key_value=None, properties=None, clause=None, clause_binding=None, dummy_name="n"
        ALL THE ARGUMENTS ARE OPTIONAL (no arguments at all means "match everything in the database")

        :param internal_id: An integer or string with the node's internal database ID.
                                If specified, it will lead to all the remaining arguments being DISREGARDED (though saved)

        :param labels:      A string (or list/tuple of strings) specifying one or more node labels.
                                (Note: blank spaces ARE allowed in the strings)
                                EXAMPLES:  "cars"
                                            ("cars", "powered vehicles")
                            Note that if multiple labels are given, then only nodes possessing ALL of them will be matched;
                            at present, there's no way to request an "OR" operation on labels

        :param key_name:    A string with the name of a node attribute; if provided, key_value must be present, too
        :param key_value:   The required value for the above key; if provided, key_name must be present, too
                                Note: no requirement for the key to be primary

        :param properties:  A (possibly-empty) dictionary of property key/values pairs, indicating a condition to match.
                                EXAMPLE: {"gender": "F", "age": 22}

        :param clause:      Either None, OR a (possibly empty) string containing a Cypher subquery,
                            OR a pair/list (string, dict) containing a Cypher subquery and the data-binding dictionary for it.
                            The Cypher subquery should refer to the node using the assigned dummy_node_name (by default, "n")
                                IMPORTANT:  in the dictionary, don't use keys of the form "n_par_i",
                                            where n is the dummy node name and i is an integer,
                                            or an Exception will be raised - those names are for internal use only
                                EXAMPLES:   "n.age < 25 AND n.income > 100000"
                                            ("n.weight < $max_weight", {"max_weight": 100})

        :param clause_binding:  A dict meant to provide the data for a clause
                                EXAMPLE:  {"max_weight": 100}

        :param dummy_name: A string with a name by which to refer to the nodes (by default, "n") in the clause;
                                only used if a `clause` argument is passed (in the absence of a clause, it's stored as None)
        
nameargumentsreturns
build_cypher_elementsdummy_name=NoneNone
        This method manages the parts of the object buildup that depend on the dummy node name.
        Primary use case:
            if called at the end of a new object's instantiation, it finalizes its construction

        Alternate use case:
            if called on an existing object, it will change its structure
                to make use of the given node dummy name, if possible, or raise an Exception if not.
                (Caution: the object will get permanently changed)

        :param dummy_name:  String with the desired dummy name to use to refer to the node in Cypher queries
        :return:            None
        
nameargumentsreturns
extract_nodestr
        Return the node information to be used in composing Cypher queries

        :return:        A string with the node information, as needed by Cypher queries.  EXAMPLES:
                            "(n  )"
                            "(p :`person` )"
                            "(n :`car`:`surplus inventory` )"
                            "(n :`person` {`gender`: $n_par_1, `age`: $n_par_2})"
        
nameargumentsreturns
extract_dummy_namestr
        Return the dummy node name to be used in composing Cypher queries

        :return:    A string with the dummy node name to use in the Cypher query (often "n" , or "to" , or "from")
        
nameargumentsreturns
unpack_matchtuple
        Return a tuple containing:
        (node, where, data_binding, dummy_node_name) ,
        for use in composing Cypher queries

        :return:    A tuple containing (node, where, data_binding, dummy_node_name)
                        1) "node":  a string, defining a node in a Cypher query,
                                    incl. parentheses but *excluding* the "MATCH" keyword
                        2) "where": a string, defining the "WHERE" part of the subquery (*excluding* the "WHERE"),
                                    if applicable;  otherwise, a blank
                        3) "data_binding":      a (possibly empty) data-binding dictionary
                        4) "dummy_node_name":   a string used for the node name inside the Cypher query (by default, "n");
                                                potentially relevant to the "node" and "where" values
        
nameargumentsreturns
extract_where_clausestr
        Cleanup the WHERE clause, and prefix the "WHERE" keyword as needed

        :return:
        
nameargumentsreturns
assert_valid_structureNone
        Verify that the object is a valid one (i.e., correctly initialized); if not, raise an Exception
        NOT IN CURRENT USE.  Possibly deprecated

        :return:        None
        



Class CypherUtils

    Helper STATIC class.
    Meant as a PRIVATE class; not indicated for the end user.
    
nameargumentsreturns
process_match_structurehandle :Union[int, str, CypherBuilder], dummy_node_name=None, caller_method=NoneCypherBuilder
        Accept either a valid internal database node ID, or a "CypherBuilder" object,
        and turn it into a "CypherBuilder" object that makes use of the requested dummy name

        Note: no database operation is performed

        :param handle:          EITHER a valid internal database ID (int or string),
                                    OR a "CypherBuilder" object (containing data to identify a node or set of nodes)

        :param dummy_node_name: [OPTIONAL] A string that will be used inside a Cypher query, to refer to nodes
        :param caller_method:   [OPTIONAL] String with name of caller method, only used for error messages

        :return:                A "CypherBuilder" object, used to identify a node,
                                    or group of nodes
        
nameargumentsreturns
assemble_cypher_blockshandle :Union[int, str, CypherBuilder], dummy_node_name=None, caller_method=Nonetuple

        :param handle:          EITHER a valid internal database ID (int or string),
                                    OR a "CypherBuilder" object (containing data to identify a node or set of nodes)

        :param dummy_node_name: [OPTIONAL] A string that will be used inside a Cypher query, to refer to nodes
        :param caller_method:   [OPTIONAL] String with name of caller method, only used for error messages

        :return:                A tuple containing (node, where, data_binding, dummy_node_name)
                                    1) "node":  a string, defining a node in a Cypher query,
                                                incl. parentheses but *excluding* the "MATCH" keyword
                                    2) "where": a string, defining the "WHERE" part of the subquery
                                                (*excluding* the "WHERE"),
                                                if applicable;  otherwise, a blank
                                    3) "data_binding":      a (possibly empty) data-binding dictionary
                                    4) "dummy_node_name":   a string used for the node name inside the Cypher query (by default, "n");
                                                            potentially relevant to the "node" and "where" values
        
nameargumentsreturns
check_match_compatibilitymatch1 :CypherBuilder, match2 :CypherBuilderNone
        If the two given "CypherBuilder" objects
        are incompatible - in terms of collision in their dummy node names -
        raise an Exception.

        :param match1:  A "CypherBuilder" object to be used to identify a node, or group of nodes
        :param match2:  A "CypherBuilder" object to be used to identify a node, or group of nodes
        :return:        None
        
nameargumentsreturns
assert_valid_internal_idinternal_id :Union[int, str]None
        Raise an Exception if the argument is not a valid internal graph database ID

        :param internal_id: Alleged internal graph database ID
        :return:            None
        
nameargumentsreturns
valid_internal_idinternal_id :Union[int, str]bool
        Return True if `internal_id` is a potentially valid ID for a graph database.
        Note that whether it's actually valid will depend on the specific graph database, which isn't known here.

        EXAMPLES:
            - Neo4j version 4 uses non-negative integers
            - Neo4j version 5 still uses non-negative integers, but also offers an alternative internal ID that is a string
            - Most other graph databases (such as Neptune) use strings

        :param internal_id: An alleged internal database ID
        :return:            True if internal_id is a valid internal database ID, or False otherwise
        
nameargumentsreturns
prepare_labelslabels :Union[str, List[str], Tuple[str]]str
        Turn the given string, or list/tuple of strings - representing one or more database node labels - into a string
        suitable for inclusion into a Cypher query.
        Blanks ARE allowed in the names.
        EXAMPLES:
            "" or None          both give rise to    ""
            "client"            gives rise to   ":`client`"
            "my label"          gives rise to   ":`my label`"
            ["car", "vehicle"]  gives rise to   ":`car`:`vehicle`"

        :param labels:  A string, or list/tuple of strings, representing one or multiple Neo4j labels;
                            it's acceptable to be None
        :return:        A string suitable for inclusion in the node part of a Cypher query
        
nameargumentsreturns
prepare_wherewhere_list: Union[str, list]str
        Given a WHERE clause, or list/tuple of them, combined them all into one -
        and also prefix the WHERE keyword to the result (if appropriate).
        The *combined* clauses of the WHERE statement are parentheses-enclosed, to protect against code injection

        EXAMPLES:   "" or "      " or [] or ("  ", "") all result in  ""
                    "n.name = 'Julian'" returns "WHERE (n.name = 'Julian')"
                        Likewise for ["n.name = 'Julian'"]
                    ("p.key1 = 123", "   ",  "p.key2 = 456") returns "WHERE (p.key1 = 123 AND p.key2 = 456)"

        :param where_list:  A string with a subclause, or list or tuple of subclauses,
                            suitable for insertion in a WHERE statement

        :return:            A string with the combined WHERE statement,
                            suitable for inclusion into a Cypher query (empty if there were no subclauses)
        
nameargumentsreturns
prepare_data_bindingdata_binding_1 :dict, data_binding_2 :dictdict
        Return the combined version of two data binding dictionaries
        (without altering the original dictionaries)

        :return:    A (possibly empty) dict with the combined data binding dictionaries,
                        suitable for inclusion into a Cypher query
        
nameargumentsreturns
dict_to_cypherdata_dict: {}, prefix="par_"(str, {})
        Turn a Python dictionary (meant for specifying node or relationship attributes)
        into a string suitable for Cypher queries,
        plus its corresponding data-binding dictionary.

        EXAMPLE :
                {'cost': 65.99, 'item description': 'the "red" button'}

                will lead to the pair:
                    (
                        '{`cost`: $par_1, `item description`: $par_2}',
                        {'par_1': 65.99, 'par_2': 'the "red" button'}
                    )

        Note that backticks are used in the Cypher string to allow blanks in the key names.
        Consecutively-named dummy variables ($par_1, $par_2, etc) are used,
        instead of names based on the keys of the data dictionary (such as $cost),
        because the keys might contain blanks.

        SAMPLE USAGE:
            (cypher_properties, data_binding) = dict_to_cypher(data_dict)

        :param data_dict:   A Python dictionary
        :param prefix:      Optional prefix string for the data-binding dummy names (parameter tokens); handy to prevent conflict;
                                by default, "par_"

        :return:            A pair consisting of a string suitable for Cypher queries,
                                and a corresponding data-binding dictionary.
                            If the passed dictionary is empty or None,
                                the pair returned is ("", {})
        
nameargumentsreturns
avoid_links_in_pathavoid_links : None | str | list | tuple, path_dummy_name="p", prefix_and=Falsestr
        Create a clause for a Cypher query to traverse a graph while avoid links with specific names.

        EXAMPLE:   MATCH p=(:start_Label)-[*]->(:end_label) WHERE {here insert the clause returned by this function} RETURN ...

        :param avoid_links:     Name, or list/tuple of names, of links to avoid in the graph traversal
        :param path_dummy_name: Whatever dummy name is being used in the overall Cypher query, to refer to the paths
        :param prefix_and:      [OPTIONAL] If True, prefix "AND " in cases where the returned value isn't a blank string;
                                    by default, False
        :return:                A Cypher clause fragment