Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/gsk-biostatistics/neointerface
NeoInterface - Neo4j made easy for Python programmers!
https://github.com/gsk-biostatistics/neointerface
database graph-database neo4j python
Last synced: 3 months ago
JSON representation
NeoInterface - Neo4j made easy for Python programmers!
- Host: GitHub
- URL: https://github.com/gsk-biostatistics/neointerface
- Owner: GSK-Biostatistics
- License: apache-2.0
- Created: 2021-12-03T12:48:09.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2024-03-20T08:38:11.000Z (10 months ago)
- Last Synced: 2024-10-14T15:05:13.410Z (3 months ago)
- Topics: database, graph-database, neo4j, python
- Language: Python
- Homepage:
- Size: 2.26 MB
- Stars: 24
- Watchers: 1
- Forks: 5
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# Neointerface - Neo4j made easy for Python programmers!
A Python interface to use the Neo4j graph database, and simplify its use.
class **NeoInterface**:
Class to interact programmatically with Neo4j using Python.
It provides a higher-level wrapper around the Neo4j python connectivity library "Neo4j Python Driver"
(https://neo4j.com/docs/api/python-driver/current/api.html)
This class reduces the need to know and use Cypher query language;
for a variety of common operations, class methods may be used instead of Cypher queries.
Advanced users can pass Cypher directly.
IMPORTANT NOTE: tested on **versions 4.3.6 and 4.4.11 of Neo4j****AUTHORS:**
Alexey Kuznetsov, Julian West, GlaxoSmithKline# MICRO-TUTORIAL
## What are Neo4j/Graph Databases, and why do they matter?
If you're new,
here's a [gentle brief intro](https://julianspolymathexplorations.blogspot.com/2021/02/neo4j-graph-databases-intro.html)
by one of the authors of this class.## In a nutshell
If you understand that:
1) Neo4j **nodes** are similar to *records* (rows) in relational databases or spreadsheets
2) Neo4j **node labels** vaguely correspond to table names (though a node can have multiple labels)
3) Neo4j **links (relationships)** are ways of pointing from a record to one or more others
(akin to "foreign keys" or "join tables" in relational databases)
4) Neo4j includes a **query language named Cypher**, akin to SQL (but much more powerful!)then you're ready to start!
## How to set up Neo4j
For use within a company, consult on which method is preferred with your IT department.*Any one of the following methods:*
* **Super-simple method for beginners**: get a [free account
at the Neo4j sandbox](https://neo4j.com/sandbox/); then use the credentials from that site
* Create or obtain a Docker container with Neo4j : see [appendix A](Appendixes.md)
* Install the free [Neo4j Community server](https://neo4j.com/download-center/#community)
on your computer - Windows10 or Linux. You will
also need [Java11](https://www.oracle.com/java/technologies/javase-jdk11-downloads.html), if
don't have it already.
* Install the Windows program [Neo4j Desktop](https://neo4j.com/download-center/#desktop) (convenient,
but be aware that you will be aggressively steered towards the Enterprise edition)
* Use a pre-made VM machine image that contains Neo4j,
for example from the excellent [Bitnami](https://bitnami.com/stack/neo4j)
* Use [Neo4j Aura](https://neo4j.com/cloud/aura/), a managed solutionNote: some functions in this class, such as export_dbase_json(), require that the *APOC* library
be installed alongside Neo4j - see [appendix B](Appendixes.md); the Docker option has APOC already enabled.
The RDF functionality requires both APOC and n10s installed.## A quick gallop thru Neo4j
```python
import neointerface
db = neointerface.NeoInterface(host="neo4j://localhost:7687" , credentials=("neo4j", "YOUR_NEO4J_PASSWORD"))# Create 2 new nodes (records). The internal Neo4j node ID is returned
node1_id = db.create_node_by_label_and_dict("patient", {'patient_id': 123, 'gender': 'M'})
node2_id = db.create_node_by_label_and_dict("doctor", {'doctor_id': 1, 'name': 'Hippocrates'})# You can think of the above as a 1-record table of patients and a 1-record table of doctors.
# Now link the patient to his doctor
db.link_nodes_by_ids(node1_id, node2_id, "IS_TREATED_BY", {'since': 2021})# You can run general Cypher queries, or use existing methods that allow you to avoid
# them for common operations# EXAMPLE: find all the patients of a doctor named 'Hippocrates'
cypher = "MATCH (p :patient)-[IS_TREATED_BY]->(d :doctor {name:'Hippocrates'}) RETURN p"
result = db.query(cypher)
print(result) # SHOWS: [{'p': {'gender': 'M', 'patient_id': 123}}]
```
The database constructed so far, as seen in the Neo4j [browser](https://browser.neo4j.io/):
![The database constructed so far, as seen in the Neo4j browser](docs/Example_database.png)## From Pandas dataframe to Neo4j and back
Let's say that you have a Pandas dataframe such as:
```python
import pandas as pd
df_original = pd.DataFrame({"patient_id": [100, 200], "name": ["Jack", "Jill"]})
```(row number) | patient_id | name
-----| ---------| -------
0| 100 | Jack
1| 200 | JillLoad it into the Neo4j database simply with:
```python
db.load_df(df_original, "my_label")
```where *db* is the instantiated NeoInterface class from the earlier example. The *"my_label"* string
roughly corresponds to table names in relational databases.
Voila', now you have 2 nodes in your database:
>**NODE 1**, with properties "patient_id"=100 and "name"="Jack"
>
>**NODE 2**, with properties "patient_id"=200 and "name"="Jill"If you want them back as a dataframe, just do:
```python
df_new = db.get_df("my_label")
```
## From dictionary to Neo4j
If you have a dictionary, for example:
```python
dictionary = {"class": "Dataset", "name": "DM", "Column": [{"name": "USUBJID"}, {"name": "DMDTC", "type": "datetime"}]}
```
Load it into the Neo4j database simply with:
```python
db.load_dict(dictionary, label="Dataset")
```
where *db* is the instantiated NeoInterface class from the earlier example, and label is the label to assign to the root node.![image](docs/load_dict.png)
# Instantiating the Class
## NeoInterface()
name | arguments| return
-----| ---------| -------
*NeoInterface*| host=os.environ.get("NEO4J_HOST"), credentials=(os.environ.get("NEO4J_USER"), os.environ.get("NEO4J_PASSWORD")), apoc=False, rdf=False, rdf_host = None, verbose=True, 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. 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
if None then no authentication is used
:param apoc: Flag indicating whether apoc library is used on Neo4j database to connect to
:param verbose: Flag indicating whether a verbose mode is to be used by all methods of this class
:param debug: Flag indicating whether a debug mode is to be used by all methods of this class
:param autoconnect Flag indicating whether the class should establish connection to database at initialization---
# Mini-UI
A mini user interface over some of the NeoInterface functionality is included in this repository. Simply set you environment variables to connect to Neo4j(NEO4J_HOST, NEO4J_USER and NEO4J_PASSWORD) and run
```source app.sh```
on Unix.# GENERAL METHODS
## version()
name | arguments| return
-----| ---------| -------
*version*| | strReturn the version of the Neo4j driver being used. EXAMPLE: '4.2.1'
---
## close()
name | arguments| return
-----| ---------| -------
*close*| | strTerminate the database connection.
Note: this method is automatically invoked after the last operation of a 'with' statement
---
# METHODS TO RUN GENERIC QUERIES
## query()
name | arguments| return
-----| ---------| -------
*query*| q: str, params = None, return_type = 'data'| list/neo4j.Result/pd.DataFrame/MultiDiGraphRuns a general Cypher query
:param q: A Cypher query
:param params: An optional Cypher dictionary
EXAMPLE, assuming that the cypher string contains the substrings "$node_id":
{'node_id': 20}
:param return_type: type of the returned result 'data'/'neo4j.Result'/'pd'/'nx'
*** When return_type == 'neo4j.Result':
Returns result of the query as a raw neo4j.Result object
(See https://neo4j.com/docs/api/python-driver/current/api.html#neo4j.Result)
*** When return_type == 'data' (default):
Returns a list of dictionaries.
In cases of error, return an empty list.
A new session to the database driver is started, and then immediately terminated after running the query.
:return: 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: 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 a single relationship, with or without attributes: MERGE (c)-[r:PAID_BY]->(p)
-> a single list item such as [{ 'r': ({}, 'PAID_BY', {}) }]
Cypher creates nodes (without returning them)
-> empty list
*** When return_type == 'pd':
Returns result of the query as a pandas dataframe
(See https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html)
by storing the values of node properties in columns of the dataframe
(note the info about the labels of the nodes is not persisted)
*** When return_type == 'nx':
Returns result of the query as a networkx graph
(See https://networkx.org/documentation/stable/reference/classes/multidigraph.html)
---## query_expanded()
name | arguments| return
-----| ---------| -------
*query_expanded*| q: str, params = None, flatten = False| []**NOTE: This procedure will be deprecated, use query(... return_type: str = 'neo4j.Result') instead**
Expanded version of query(), meant to extract additional info for queries that return Graph Data Types,
i.e. nodes, relationships or paths,
such as "MATCH (n) RETURN n", or "MATCH (n1)-[r]->(n2) RETURN r"For example, if nodes were returned, and their Neo4j internal 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
:param params: 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.:return: A (possibly empty) list of dictionaries, which will depend on which Graph Data Types
were returned in the Cypher query.
EXAMPLE - for a returned node
{'gender': 'M', 'age': 20, 'neo4j_id': 123, 'neo4j_labels': ['patient']}
EXAMPLE - for a returned relationship
{'price': 7500, 'neo4j_id': 2,
'neo4j_start_node': ,
'neo4j_end_node': ,
'neo4j_type': 'bought_by'}]---
# METHODS TO RETRIEVE DATA
## get_single_field()
name | arguments| return
-----| ---------| -------
*get_single_field*| labels, field_name: str, properties_condition=None, cypher_clause=None, cypher_dict=None| listFor situations where one is fetching just 1 field,
and one desires a list of those values, rather than a dictionary of records.
In other respects, similar to the more general get_nodes()EXAMPLES: fetch_single_field("car", "price", properties_condition={"car_make": "Toyota"})
will RETURN a list of prices of all the Toyota models
fetch_single_field("car", "price", properties_condition={"car_make": "Toyota"}, clause="n.price < 50000")
will RETURN a list of prices of all the Toyota models that cost less than 50000:param field_name: A string with the name of the desired field (attribute)
For more information on the other parameters, see get_nodes()
:return: A list of the values of the field_name attribute in the nodes that match the specified conditions
---
![image](docs/get_single_field.png)
## get_nodes()
name | arguments| return
-----| ---------| -------
*get_nodes*| labels="", properties_condition=None, cypher_clause=None, cypher_dict=None, return_nodeid=False, return_labels=False| [{}]EXAMPLES:
get_nodes("") # Get ALL nodes
get_nodes("client")
get_nodes("client", properties_condition = {"gender": "M", "ethnicity": "white"})
get_nodes("client", cypher_clause = "n.age > 40 OR n.income < 50000")
get_nodes("client", cypher_clause = "n.age > $some_age", cypher_dict = {"$some_age": 40})
get_nodes("client", properties_condition = {"gender": "M", "ethnicity": "white"} ,
cypher_clause = "n.age > 40 OR n.income < 50000")
RETURN a list of the records (as dictionaries of ALL the key/value node properties)
corresponding to all the Neo4j nodes with the specified label,
AND satisfying the given Cypher CLAUSE (if present),
AND exactly matching ALL of the specified property key/values pairs (if present).
I.e. an implicit AND operation.
IMPORTANT: nodes referred to in the Cypher clause must be specified as "n."A dictionary of data binding (cypher_dict) for the Cypher clause may be optionally specified.
In case of conflict (any key overlap) between the dictionaries cypher_dict and properties_condition, and Exception is raised.
Optionally, the Neo4j internal node ID and label name(s) may also be obtained and returned.:param labels: A string (or list/tuple of strings) specifying one or more Neo4j labels;
an empty string indicates that the match is to be carried out
across all labels - NOT RECOMMENDED for large databases!
(Note: blank spaces ARE allowed in the strings)
:param cypher_dict: Dictionary of data binding for the Cypher string. EXAMPLE: {"gender": "M", "age": 40}
:param cypher_clause: String with a clause to refine the search; any nodes it refers to, MUST be specified as "n."
EXAMPLE with hardwired values: "n.age > 40 OR n.income < 50000"
EXAMPLE with data-binding: "n.age > $age OR n.income < $income"
(data-binding values are specified in cypher_dict)
:param properties_condition: A (possibly-empty) dictionary of property key/values pairs. Example: {"gender": "M", age: 64}
IMPORTANT: cypher_dict and properties_dict must have no overlapping keys, or an Exception will be raised
:param return_nodeid: Flag indicating whether to also include the Neo4j internal node ID in the returned data
(using "neo4j_id" as its key in the returned dictionary)
:param return_labels: Flag indicating whether to also include the Neo4j label names in the returned data
(using "neo4j_labels" as its key in the returned dictionary)
:return: A 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 "neo4j_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 "neo4j_labels": [list of Neo4j label(s) attached to that node]
EXAMPLE using both of the above flags:
[ {"neo4j_id": 145, "neo4j_labels": ["person", "client"], "gender": "M", "age": 42, "condition_id": 3},
{"neo4j_id": 222, "neo4j_labels": ["person"], "gender": "M", "age": 76, "location": "Berkeley"}
]---
## get_df()
name | arguments| return
-----| ---------| -------
*get_df*| labels="", properties_condition=None, cypher_clause=None, cypher_dict=None, return_nodeid=False, return_labels=False| pd.DataFrameSame as get_nodes(), but the result is returned as a Pandas dataframe
See get_nodes() for more information
---
## get_parents_and_children()
name | arguments| return
-----| ---------| -------
*get_parents_and_children*| node_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 node_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: "id", "label", "rel"
EXAMPLE of individual items in either parent_list or child_list:
{'id': 163, 'labels': ['Subject'], 'rel': 'HAS_TREATMENT'}---
## get_labels()
name | arguments| return
-----| ---------| -------
*get_labels*| | [str]Extract and return a list of all the Neo4j labels present in the database.
No particular order should be expected.:return: A list of strings
---
## get_relationshipTypes()
name | arguments| return
-----| ---------| -------
*get_relationshipTypes*| | [str]Extract and return a list of all the Neo4j relationship types present in the database.
No particular order should be expected.
:return: A list of strings---
## get_label_properties()
name | arguments| return
-----| ---------| -------
*get_label_properties*| label:str| listReturn a list of keys associated to any node with the given label
---
# METHODS TO GET/CREATE/MODIFY SCHEMA
## get_indexes()
name | arguments| return
-----| ---------| -------
*get_indexes*| types=None| pd.DataFrameReturn all the database indexes, and some of their attributes,
as a Pandas dataframe.
Optionally restrict the type (such as "BTREE") of indexes returned.EXAMPLE:
labelsOrTypes name properties type uniqueness
0 [my_label] index_23b0962b [my_property] BTREE NONUNIQUE
1 [my_label] some_name [another_property] BTREE UNIQUE:param types: Optional list to of types to limit the result to
:return: A (possibly-empty) Pandas dataframe---
## get_constraints()
name | arguments| return
-----| ---------| -------
*get_constraints*| | pd.DataFrameReturn 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
---
## create_index()
name | arguments| return
-----| ---------| -------
*create_index*| label: str, key: str| boolCreate a new database index, unless it already exists,
to be applied to the specified label and key (property).
The standard name given to the new index is of the form label.keyEXAMPLE - to index nodes labeled "car" by their key "color":
create_index("car", "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---
## create_constraint()
name | arguments| return
-----| ---------| -------
*create_constraint*| label: str, key: str, type="UNIQUE", name=None| boolCreate a uniqueness constraint for a node property in the graph,
unless a constraint with the standard name of the form `{label}.{key}.{type}` is already presentNote: 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 type: For now, the default "UNIQUE" is the only allowed option
:param name: Optional name to give to the new constraint; if not provided, a
standard name of the form `{label}.{key}.{type}` is used. EXAMPLE: "patient.patient_id.UNIQUE"
:return: True if a new constraint was created, or False otherwise---
## drop_index()
name | arguments| return
-----| ---------| -------
*drop_index*| name: str| boolEliminate the index with the specified name.
:param name: Name of the index to eliminate
:return: True if successful or False otherwise (for example, if the index doesn't exist)---
## drop_constraint()
name | arguments| return
-----| ---------| -------
*drop_constraint*| name: str| boolEliminate 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)---
## drop_all_constraints()
name | arguments| return
-----| ---------| -------
*drop_all_constraints*| | NoneEliminate all the constraints in the database
:return: None
---
## drop_all_indexes()
name | arguments| return
-----| ---------| -------
*drop_all_indexes*| including_constraints=True| NoneEliminate 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---
# METHODS TO CREATE/MODIFY DATA
## create_node_by_label_and_dict()
name | arguments| return
-----| ---------| -------
*create_node_by_label_and_dict*| label: str, items=None| intCreate a new node with the given label and with the attributes/values specified in the items dictionary
Return the Neo4j internal ID of the node just created.:param label: A string with a Neo4j label (ok to include blank spaces)
:param items: An optional dictionary. EXAMPLE: {'age': 22, 'gender': 'F'}
:return: An integer with the Neo4j internal ID of the node just created---
## delete_nodes_by_label()
name | arguments| return
-----| ---------| -------
*delete_nodes_by_label*| delete_labels=None, keep_labels=None, batch_size = 50000| NoneEmpty 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! To get rid of those, run drop_all_indexes():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---
## clean_slate()
name | arguments| return
-----| ---------| -------
*clean_slate*| keep_labels=None, drop_indexes=True, drop_constraints=True| NoneUse this to get rid of absolutely everything in the database.
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---
## set_fields()
name | arguments| return
-----| ---------| -------
*set_fields*| labels, set_dict, properties_condition=None, cypher_clause=None, cypher_dict=None| NoneEXAMPLE - locate the "car" with vehicle id 123 and set its color to white and price to 7000
set_fields(labels = "car", set_dict = {"color": "white", "price": 7000},
properties_condition = {"vehicle id": 123})LIMITATION: blanks are allowed in the keys of properties_condition, but not in those of set_dict
:param labels: A string, or list/tuple of strings, representing Neo4j labels
:param set_dict: A dictionary of field name/values to create/update the node's attributes
(note: no blanks are allowed in the keys)
:param properties_condition:
:param cypher_clause:
:param cypher_dict:
:return: None---
## extract_entities()
name | arguments| return
-----| ---------| -------
*extract_entities*| mode='merge', label=None, cypher=None, cypher_dict=None, target_label=None, property_mapping={}, relationship=None, direction=None| None
Create new nodes using data from other nodes
Example use:
df = pd.DataFrame({'id': [1, 2, 3, 4, 5], 'color': ['red', 'red', 'red', 'blue', 'blue']})
db.load_df(df, label='Thing')
db.extract_entities(
label='Thing',
target_label='Color',
relationship='OF',
property_mapping=['color'])
:param mode:str; assert mode in ['merge', 'create']
:param label:str; label of the nodes to extract data from
:param cypher: str; only of label not provided: cypher that returns id(node) of the nodes to extract data from
EXAMPLE:
cypher = '''
MATCH (f:`Source Data Table`{{_domain_:$domain}})-[:HAS_DATA]->(node:`Source Data Row`)
RETURN id(node)
'''
:param cypher_dict: None/dict parameters required for the cypher query
EXAMPLE:
cypher_dict={'domain':'ADSL'}
:param target_label: label of the newly created nodes with extracted data
:param property_mapping: dict or list
if dict: keys correspond to the property names of source data (e.g. Source Data Row) and values correspond
to to the property names of the target class where the data is extracted to
if list: properties of the extracted node (as per the list) will extracted and will be named same as
in the source node
:param relationship: type of the relationship (to/from the extraction node) to create
:param direction: direction of the relationship to create (>: to the extraction node, <: from the extraction node)
:return: None
![extract_entities](docs/extract_entities.png)
The part in green would be created as the result of operation in the "Example use".
---# METHODS TO CREATE NEW RELATIONSHIPS
## link_entities()
name | arguments| return
-----| ---------| -------
*link_entities*| left_class:str, right_class:str, relationship="\_default_", cond_via_node=None, cond_left_rel=None, cond_right_rel=None, cond_cypher=None, cond_cypher_dict=None| NoneCreates relationship of type {relationship}
Example use:
db.link_entities(left_class='Thing', right_class='Thing',
cond_via_node="Color",
cond_left_rel=" or or