From 1970cd3c9d84313cf43ebd936773c9c55fec829b Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Fri, 15 Mar 2024 14:17:48 +0000 Subject: [PATCH 01/16] feat: resultSet sugar for Graph Visulization result = session.execute('GET SUBGRAPH WITH PROP 2 STEPS FROM "player101" YIELD VERTICES AS nodes, EDGES AS relationships;') v = result.dict_for_vis() implement: https://github.com/vesoft-inc/nebula-python/issues/318 --- nebula3/data/ResultSet.py | 166 +++++++++++++++++++++++++++++++++++++- 1 file changed, 165 insertions(+), 1 deletion(-) diff --git a/nebula3/data/ResultSet.py b/nebula3/data/ResultSet.py index b9a5954b..2f87a8d9 100644 --- a/nebula3/data/ResultSet.py +++ b/nebula3/data/ResultSet.py @@ -8,7 +8,7 @@ from nebula3.common.ttypes import ErrorCode -from nebula3.data.DataObject import DataSetWrapper +from nebula3.data.DataObject import DataSetWrapper, Node, Relationship, PathWrapper class ResultSet(object): @@ -191,6 +191,170 @@ def rows(self): if self._data_set_wrapper is None: return [] return self._data_set_wrapper.get_rows() + + def dict_for_vis(self): + """Convert result set to a dictionary format suitable for visualization. + + Example: + { + "nodes":[ + { + "id":"player100", + "labels":[ + "player" + ], + "props":{ + "name":"Tim Duncan", + "age":"42", + "id":"player100" + } + }, + { + "id":"player101", + "labels":[ + "player" + ], + "props":{ + "age":"36", + "name":"Tony Parker", + "id":"player101" + } + } + ], + "edges":[ + { + "src":"player100", + "dst":"player101", + "name":"follow", + "rank":0, + "props":{ + "degree":"95" + } + } + ], + "nodes_dict":{ + "player100":{ + "id":"player100", + "labels":[ + "player" + ], + "props":{ + "name":"Tim Duncan", + "age":"42", + "id":"player100" + } + }, + "player101":{ + "id":"player101", + "labels":[ + "player" + ], + "props":{ + "age":"36", + "name":"Tony Parker", + "id":"player101" + } + } + }, + "edges_dict":{ + "(""player100", + "player101", + 0, + "follow"")":{ + "src":"player100", + "dst":"player101", + "name":"follow", + "rank":0, + "props":{ + "degree":"95" + } + } + }, + "nodes_count":2, + "edges_count":1 + } + + :return: dict with keys: + nodes, edges, nodes_dict, edges_dict, nodes_count, edges_count + """ + def add_to_nodes_or_edges(nodes_dict, edges_dict, item): + if isinstance(item, Node): + node_id = str(item.get_id().cast()) + tags = item.tags() # list of strings + props_raw = dict() + for tag in tags: + # TODO: handle duplicate keys + props_raw.update(item.properties(tag)) + props = { + k: str(v.cast()) if hasattr(v, "cast") else str(v) + for k, v in props_raw.items() + } + + if "id" not in props: + props["id"] = node_id + + if node_id not in nodes_dict: + nodes_dict[node_id] = { + "id": node_id, + "labels": tags, + "props": props, + } + else: + nodes_dict[node_id]["labels"] = list(set(nodes_dict[node_id]["labels"] + tags)) + nodes_dict[node_id]["props"].update(props) + + elif isinstance(item, Relationship): + src_id = str(item.start_vertex_id().cast()) + dst_id = str(item.end_vertex_id().cast()) + rank = item.ranking() + edge_name = item.edge_name() + props_raw = item.properties() + props = { + k: str(v.cast()) if hasattr(v, "cast") else str(v) + for k, v in props_raw.items() + } + if (src_id, dst_id, rank, edge_name) not in edges_dict: + edges_dict[(src_id, dst_id, rank, edge_name)] = { + "src": src_id, + "dst": dst_id, + "name": edge_name, + "rank": rank, + "props": props, + } + else: + edges_dict[(src_id, dst_id, rank, edge_name)]["props"].update(props) + + elif isinstance(item, PathWrapper): + for node in item.nodes(): + add_to_nodes_or_edges(nodes_dict, edges_dict, node) + for edge in item.relationships(): + add_to_nodes_or_edges(nodes_dict, edges_dict, edge) + + elif isinstance(item, list): + for it in item: + add_to_nodes_or_edges(nodes_dict, edges_dict, it) + + nodes_dict = dict() + edges_dict = dict() + + columns = self.keys() + for col_num in range(self.col_size()): + col_name = columns[col_num] + col_list = self.column_values(col_name) + add_to_nodes_or_edges( + nodes_dict, edges_dict, [x.cast() for x in col_list] + ) + nodes = list(nodes_dict.values()) + edges = list(edges_dict.values()) + + return { + "nodes": nodes, + "edges": edges, + "nodes_dict": nodes_dict, + "edges_dict": edges_dict, + "nodes_count": len(nodes), + "edges_count": len(edges), + } def __iter__(self): """the iterator for per row From baeb0952cac76737dd1bb423a20d35d10db06f3c Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Fri, 15 Mar 2024 14:29:56 +0000 Subject: [PATCH 02/16] pop rank to prop --- nebula3/data/ResultSet.py | 133 ++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 70 deletions(-) diff --git a/nebula3/data/ResultSet.py b/nebula3/data/ResultSet.py index 2f87a8d9..c101d931 100644 --- a/nebula3/data/ResultSet.py +++ b/nebula3/data/ResultSet.py @@ -197,81 +197,68 @@ def dict_for_vis(self): Example: { - "nodes":[ - { - "id":"player100", - "labels":[ - "player" - ], - "props":{ - "name":"Tim Duncan", - "age":"42", - "id":"player100" - } - }, - { - "id":"player101", - "labels":[ - "player" - ], - "props":{ - "age":"36", - "name":"Tony Parker", - "id":"player101" + 'nodes': [ + { + 'id': 'player100', + 'labels': ['player'], + 'props': { + 'name': 'Tim Duncan', + 'age': '42', + 'id': 'player100' + } + }, + { + 'id': 'player101', + 'labels': ['player'], + 'props': { + 'age': '36', + 'name': 'Tony Parker', + 'id': 'player101' + } } - } - ], - "edges":[ - { - "src":"player100", - "dst":"player101", - "name":"follow", - "rank":0, - "props":{ - "degree":"95" + ], + 'edges': [ + { + 'src': 'player100', + 'dst': 'player101', + 'name': 'follow', + 'props': { + 'degree': '95' + } } - } - ], - "nodes_dict":{ - "player100":{ - "id":"player100", - "labels":[ - "player" - ], - "props":{ - "name":"Tim Duncan", - "age":"42", - "id":"player100" + ], + 'nodes_dict': { + 'player100': { + 'id': 'player100', + 'labels': ['player'], + 'props': { + 'name': 'Tim Duncan', + 'age': '42', + 'id': 'player100' + } + }, + 'player101': { + 'id': 'player101', + 'labels': ['player'], + 'props': { + 'age': '36', + 'name': 'Tony Parker', + 'id': 'player101' + } } }, - "player101":{ - "id":"player101", - "labels":[ - "player" - ], - "props":{ - "age":"36", - "name":"Tony Parker", - "id":"player101" - } - } - }, - "edges_dict":{ - "(""player100", - "player101", - 0, - "follow"")":{ - "src":"player100", - "dst":"player101", - "name":"follow", - "rank":0, - "props":{ - "degree":"95" + 'edges_dict': { + ('player100', 'player101', 0, 'follow'): { + 'src': 'player100', + 'dst': 'player101', + 'name': 'follow', + 'props': { + 'degree': '95' + } } - } - }, - "nodes_count":2, - "edges_count":1 + }, + 'nodes_count': 2, + 'edges_count': 1 } :return: dict with keys: @@ -346,6 +333,12 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): ) nodes = list(nodes_dict.values()) edges = list(edges_dict.values()) + # move rank to props, omit rank 0 + for edge in edges: + if "rank" in edge: + rank = edge.pop("rank") + if rank != 0: + edge["props"]["rank"] = rank return { "nodes": nodes, From 74469cd58919270a03d851fa40081e9442a566fb Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Fri, 15 Mar 2024 16:36:41 +0000 Subject: [PATCH 03/16] feat: resultSet.as_pandas() && ValueWrapper.cast_primitive() --- nebula3/data/DataObject.py | 74 ++++++++++++++++++++++++++++++++++++++ nebula3/data/ResultSet.py | 56 ++++++++++++++++++++--------- 2 files changed, 114 insertions(+), 16 deletions(-) diff --git a/nebula3/data/DataObject.py b/nebula3/data/DataObject.py index 5d3e83fd..a2948dfc 100644 --- a/nebula3/data/DataObject.py +++ b/nebula3/data/DataObject.py @@ -699,6 +699,80 @@ def cast(self) -> Any: if _type == Value.MVAL: return {k: v.cast() for k, v in self.as_map().items()} + def cast_primitive(self) -> Any: + """ + automatically convert value wrapper to primitive type by calling casting method. + + : return: Any type (e.g. int, float, str, bool) + """ + + def _cast_node(node: Vertex): + return { + "vid": node.get_id().cast(), + "tags": node.tags(), + "props": node.properties(), + } + + def _cast_relationship(edge: Edge): + return { + "src": edge.start_vertex_id().cast(), + "dst": edge.end_vertex_id().cast(), + "type": edge.edge_name(), + "rank": edge.ranking(), + "props": edge.properties(), + } + + def _cast_primitive(raw_value): + _type = raw_value._value.getType() + if _type == Value.__EMPTY__: + return None + elif _type == Value.NVAL: + return None + elif _type == Value.BVAL: + return raw_value.as_bool() + elif _type == Value.IVAL: + return raw_value.as_int() + elif _type == Value.FVAL: + return raw_value.as_double() + elif _type == Value.SVAL: + return raw_value.as_string() + elif _type == Value.LVAL: + return [_cast_primitive(x) for x in raw_value.as_list()] + elif _type == Value.UVAL: + return {_cast_primitive(x) for x in raw_value.as_set()} + elif _type == Value.MVAL: + return {k: _cast_primitive(v) for k, v in raw_value.as_map().items()} + elif _type == Value.TVAL: + return raw_value.as_time().get_local_time_str() + elif _type == Value.DTVAL: + return raw_value.as_date().__repr__() + elif _type == Value.DTVAL: + return raw_value.as_datetime().get_local_datetime_str() + elif _type == Value.VVAL: + return _cast_node(raw_value.as_node()) + elif _type == Value.EVAL: + return _cast_relationship(raw_value.as_relationship()) + elif _type == Value.PVAL: + path = raw_value.as_path() + return { + "path_str": path.__repr__(), + "start_node": _cast_node(path.start_node()), + "edges": [_cast_relationship(x) for x in path.relationships()], + "nodes": [_cast_node(x) for x in path.nodes()], + } + elif _type == Value.GGVAL: + return raw_value.as_geography().__repr__() + elif _type == Value.DUVAL: + return raw_value.as_duration().__repr__() + else: + raise RuntimeError( + "Unsupported type:{} to cast primitive".format( + raw_value._get_type_name() + ) + ) + + return _cast_primitive(self) + def _get_type_name(self): if self.is_empty(): return "empty" diff --git a/nebula3/data/ResultSet.py b/nebula3/data/ResultSet.py index c101d931..7bafccdc 100644 --- a/nebula3/data/ResultSet.py +++ b/nebula3/data/ResultSet.py @@ -191,7 +191,7 @@ def rows(self): if self._data_set_wrapper is None: return [] return self._data_set_wrapper.get_rows() - + def dict_for_vis(self): """Convert result set to a dictionary format suitable for visualization. @@ -202,8 +202,8 @@ def dict_for_vis(self): 'id': 'player100', 'labels': ['player'], 'props': { - 'name': 'Tim Duncan', - 'age': '42', + 'name': 'Tim Duncan', + 'age': '42', 'id': 'player100' } }, @@ -211,8 +211,8 @@ def dict_for_vis(self): 'id': 'player101', 'labels': ['player'], 'props': { - 'age': '36', - 'name': 'Tony Parker', + 'age': '36', + 'name': 'Tony Parker', 'id': 'player101' } } @@ -232,8 +232,8 @@ def dict_for_vis(self): 'id': 'player100', 'labels': ['player'], 'props': { - 'name': 'Tim Duncan', - 'age': '42', + 'name': 'Tim Duncan', + 'age': '42', 'id': 'player100' } }, @@ -241,8 +241,8 @@ def dict_for_vis(self): 'id': 'player101', 'labels': ['player'], 'props': { - 'age': '36', - 'name': 'Tony Parker', + 'age': '36', + 'name': 'Tony Parker', 'id': 'player101' } } @@ -264,13 +264,14 @@ def dict_for_vis(self): :return: dict with keys: nodes, edges, nodes_dict, edges_dict, nodes_count, edges_count """ + def add_to_nodes_or_edges(nodes_dict, edges_dict, item): if isinstance(item, Node): node_id = str(item.get_id().cast()) tags = item.tags() # list of strings props_raw = dict() for tag in tags: - # TODO: handle duplicate keys + # TODO: handle duplicate keys among tags props_raw.update(item.properties(tag)) props = { k: str(v.cast()) if hasattr(v, "cast") else str(v) @@ -279,7 +280,7 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): if "id" not in props: props["id"] = node_id - + if node_id not in nodes_dict: nodes_dict[node_id] = { "id": node_id, @@ -287,7 +288,9 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): "props": props, } else: - nodes_dict[node_id]["labels"] = list(set(nodes_dict[node_id]["labels"] + tags)) + nodes_dict[node_id]["labels"] = list( + set(nodes_dict[node_id]["labels"] + tags) + ) nodes_dict[node_id]["props"].update(props) elif isinstance(item, Relationship): @@ -310,7 +313,7 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): } else: edges_dict[(src_id, dst_id, rank, edge_name)]["props"].update(props) - + elif isinstance(item, PathWrapper): for node in item.nodes(): add_to_nodes_or_edges(nodes_dict, edges_dict, node) @@ -328,9 +331,7 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): for col_num in range(self.col_size()): col_name = columns[col_num] col_list = self.column_values(col_name) - add_to_nodes_or_edges( - nodes_dict, edges_dict, [x.cast() for x in col_list] - ) + add_to_nodes_or_edges(nodes_dict, edges_dict, [x.cast() for x in col_list]) nodes = list(nodes_dict.values()) edges = list(edges_dict.values()) # move rank to props, omit rank 0 @@ -349,6 +350,29 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): "edges_count": len(edges), } + def as_pandas(self, primitive: bool = True): + """Convert result set to a pandas DataFrame. + + :param primitive: if True, convert all values to primitive types + :return: pandas DataFrame + """ + try: + import pandas as pd + except ImportError: + raise ImportError("pandas is not installed") + + if self.is_empty(): + return pd.DataFrame() + + data = dict() + for col in self.keys(): + if primitive: + data[col] = [x.cast_primitive() for x in self.column_values(col)] + else: + data[col] = [x.cast() for x in self.column_values(col)] + + return pd.DataFrame(data) + def __iter__(self): """the iterator for per row From c303cad776d33077591946bf846ab9116b5b96fa Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Fri, 15 Mar 2024 17:59:48 +0000 Subject: [PATCH 04/16] docs and tests --- README.md | 217 +++++++++++++++++++++++++++++++--------- example/FormatResp.py | 13 +++ tests/test_data_type.py | 133 +++++++++++++++++++++++- 3 files changed, 312 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 092f4fad..ab48ccd8 100644 --- a/README.md +++ b/README.md @@ -1,48 +1,49 @@ # nebula-python -This repository holds the official Python API for NebulaGraph. +This repository contains the official Python API for NebulaGraph. [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev) [![pypi-version](https://img.shields.io/pypi/v/nebula3-python)](https://pypi.org/project/nebula3-python/) [![python-version](https://img.shields.io/badge/python-3.6.2+%20|%203.7%20|%203.8%20|%203.9%20|%203.10%20|%203.11%20|%203.12-blue)](https://www.python.org/) +## Getting Started -## Before you start +Before proceeding, it's crucial to choose the correct branch that suits your requirements. To ascertain the compatibility between the API and NebulaGraph Database, refer to the [Capability Matrix](#Capability-Matrix) section. It's worth noting that the master branch is currently tailored for NebulaGraph 3.x. -Before you start, please read this section to choose the right branch for you. The compatibility between the API and NebulaGraph service can be found in [How to choose nebula-python](#How-to-choose-nebula-python). The current master branch is compatible with NebulaGraph 3.x. - -## The directory structure +## Directory Structure Overview ```text -|--nebula-python - | - |-- nebula3 // client code - | |-- fbthrift // the fbthrift lib code - | |-- common - | |-- data - | |-- graph - | |-- meta - | |-- net // the net code for graph client - | |-- storage - | |-- Config.py // the pool config - | |__ Exception.py // the define exception - | - |-- examples - | |-- GraphClientMultiThreadExample.py // the multi thread example - | |-- GraphClientSimpleExample.py // the simple example - | |__ ScanVertexEdgeExample.py - | - |-- tests // the test code - | - |-- setup.py // used to install or package - | - |__ README.md // the introduction of nebula3-python - -``` - -## How to get nebula3-python - -### Option one: install with pip +└──nebula-python +    │ +    ├── nebula3 // client source code +    │   ├── fbthrift // the fbthrift lib code +    │   ├── common +    │   ├── data +    │   ├── graph +    │   ├── meta +    │   ├── net // the net code for graph client +    │   ├── storage +    │   ├── Config.py // the pool config +    │   └── Exception.py // the define exception +    │ +    ├── examples +    │   ├── FormatResp.py // the format response example +    │   ├── SessionPoolExample.py // the session pool example +    │   ├── GraphClientMultiThreadExample.py // the multi thread example +    │   ├── GraphClientSimpleExample.py // the simple example +    │   └── ScanVertexEdgeExample.py // the scan vertex and edge example(storage client) +    │ +    ├── tests // the test code +    │ +    ├── setup.py // used to install or package + │ + └── README.md // the introduction of nebula3-python + +``` + +## Obtaining nebula3-python + +### Method 1: Installation via pip ```python # for v3.x @@ -51,7 +52,10 @@ pip install nebula3-python==$version pip install nebula2-python==$version ``` -### Option two: install from the source code +### Method 2: Installation via source + +
+Click to expand - Clone from GitHub @@ -74,7 +78,9 @@ pip install . python3 setup.py install ``` -## Quick example to use graph-client to connect graphd +
+ +## Quick Example: Connecting to GraphD Using Graph Client ```python from nebula3.gclient.net import ConnectionPool @@ -93,7 +99,7 @@ ok = connection_pool.init([('127.0.0.1', 9669)], config) session = connection_pool.get_session('root', 'nebula') # select space -session.execute('USE nba') +session.execute('USE basketballplayer') # show tags result = session.execute('SHOW TAGS') @@ -104,7 +110,7 @@ session.release() # option 2 with session_context, session will be released automatically with connection_pool.session_context('root', 'nebula') as session: - session.execute('USE nba') + session.execute('USE basketballplayer') result = session.execute('SHOW TAGS') print(result) @@ -112,17 +118,121 @@ with connection_pool.session_context('root', 'nebula') as session: connection_pool.close() ``` -## Example of using session pool +## Using the Session Pool: A Guide + +The session pool is a collection of sessions that are managed by the pool. It is designed to improve the efficiency of session management and to reduce the overhead of session creation and destruction. + +Session Pool comes with the following assumptions: + +1. A space must already exist in the database prior to the initialization of the session pool. +2. Each session pool is associated with a single user and a single space to ensure consistent access control for the user. For instance, a user may possess different access permissions across various spaces. To execute queries in multiple spaces, consider utilizing several session pools. +3. Whenever `sessionPool.execute()` is invoked, the session executes the query within the space specified in the session pool configuration. +4. It is imperative to avoid executing commands through the session pool that would alter passwords or remove users. + +For more details, see [SessionPoolExample.py](example/SessionPoolExample.py). + +## Example: Extracting Edge and Vertex Lists from Query Results + +For graph visualization purposes, the following code snippet demonstrates how to effortlessly extract lists of edges and vertices from any query result by utilizing the `ResultSet.dict_for_vis()` method. + +```python +result = session.execute( + 'GET SUBGRAPH WITH PROP 2 STEPS FROM "player101" YIELD VERTICES AS nodes, EDGES AS relationships;') + +data_for_vis = result.dict_for_vis() +``` + +And it looks like this: + +
+ Click to expand + +```json +{ + 'nodes': [ + { + 'id': 'player100', + 'labels': ['player'], + 'props': { + 'name': 'Tim Duncan', + 'age': '42', + 'id': 'player100' + } + }, + { + 'id': 'player101', + 'labels': ['player'], + 'props': { + 'age': '36', + 'name': 'Tony Parker', + 'id': 'player101' + } + } + ], + 'edges': [ + { + 'src': 'player100', + 'dst': 'player101', + 'name': 'follow', + 'props': { + 'degree': '95' + } + } + ], + 'nodes_dict': { + 'player100': { + 'id': 'player100', + 'labels': ['player'], + 'props': { + 'name': 'Tim Duncan', + 'age': '42', + 'id': 'player100' + } + }, + 'player101': { + 'id': 'player101', + 'labels': ['player'], + 'props': { + 'age': '36', + 'name': 'Tony Parker', + 'id': 'player101' + } + } + }, + 'edges_dict': { + ('player100', 'player101', 0, 'follow'): { + 'src': 'player100', + 'dst': 'player101', + 'name': 'follow', + 'props': { + 'degree': '95' + } + } + }, + 'nodes_count': 2, + 'edges_count': 1 +} +``` + +
+ +## Example: Fetching Query Results into a Pandas DataFrame + +> For `nebula3-python>=3.6.0`: + +Assuming you have pandas installed, you can use the following code to fetch query results into a pandas DataFrame: + +```bash +pip3 install pandas ``` -There are some limitations while using the session pool: -1. There MUST be an existing space in the DB before initializing the session pool. -2. Each session pool is corresponding to a single USER and a single Space. This is to ensure that the user's access control is consistent. i.g. The same user may have different access privileges in different spaces. If you need to run queries in different spaces, you may have multiple session pools. -3. Every time when sessinPool.execute() is called, the session will execute the query in the space set in the session pool config. -4. Commands that alter passwords or drop users should NOT be executed via session pool. +```python +result = session.execute('') +df = result.to_pandas() ``` -see /example/SessinPoolExample.py -## Quick example to fetch result to dataframe + +
+ For `nebula3-python<3.6.0`: ```python from nebula3.gclient.net import ConnectionPool @@ -165,7 +275,14 @@ connection_pool.close() ``` -## Quick example to use storage-client to scan vertex and edge +
+ +## Quick Example: Using Storage Client to Scan Vertices and Edges + +Storage Client enables you to scan vertices and edges from the storage service instead of the graph service w/ nGQL/Cypher. This is useful when you need to scan a large amount of data. + +
+ Click to expand You should make sure the scan client can connect to the address of storage which see from `SHOW HOSTS` @@ -205,7 +322,11 @@ while resp.has_next(): print(edge_data) ``` -## How to choose nebula-python +
+ +See [ScanVertexEdgeExample.py](example/ScanVertexEdgeExample.py) for more details. + +## Capability Matrix | Nebula-Python Version | NebulaGraph Version | | --------------------- | ------------------- | diff --git a/example/FormatResp.py b/example/FormatResp.py index 1ce40835..98a059c6 100644 --- a/example/FormatResp.py +++ b/example/FormatResp.py @@ -15,8 +15,21 @@ from nebula3.data.ResultSet import ResultSet +################################ +# Method 0 (Recommended) # +# nebula3-python>=3.6.0 # +################################ +def result_to_df(result: ResultSet) -> pd.DataFrame: + """ + build list for each column, and transform to dataframe + """ + assert result.is_succeeded() + return result.as_pandas() + + ################################ # Method 1 (Recommended) # +# nebula3-python<=3.5.0 # ################################ def result_to_df(result: ResultSet) -> pd.DataFrame: """ diff --git a/tests/test_data_type.py b/tests/test_data_type.py index a457ef3b..f02a2afc 100644 --- a/tests/test_data_type.py +++ b/tests/test_data_type.py @@ -11,24 +11,24 @@ from nebula3.common import ttypes from nebula3.common.ttypes import ( - Date, DateTime, Duration, + Edge, ErrorCode, NList, NMap, NSet, NullType, Time, - Value, + Vertex, ) + from nebula3.data.DataObject import ( DataSetWrapper, DateTimeWrapper, DateWrapper, DurationWrapper, GeographyWrapper, - Node, Null, PathWrapper, Relationship, @@ -381,6 +381,133 @@ def test_cast(self): ] assert list_val == expect_result + def test_cast_primitive(self): + # Test casting for primitive types + def _cast_node(node: Vertex): + return { + "vid": node.get_id().cast(), + "tags": node.tags(), + "props": node.properties(), + } + + def _cast_relationship(edge: Edge): + return { + "src": edge.start_vertex_id().cast(), + "dst": edge.end_vertex_id().cast(), + "type": edge.edge_name(), + "rank": edge.ranking(), + "props": edge.properties(), + } + + # Test boolean + bool_val = ttypes.Value(bVal=True) + assert ValueWrapper(bool_val).cast_primitive() is True + + # Test integer + int_val = ttypes.Value(iVal=42) + assert ValueWrapper(int_val).cast_primitive() == 42 + + # Test double + double_val = ttypes.Value(fVal=3.14) + assert ValueWrapper(double_val).cast_primitive() == 3.14 + + # Test string + string_val = ttypes.Value(sVal=b"hello") + assert ValueWrapper(string_val).cast_primitive() == "hello" + + # Test null + null_val = ttypes.Value(nVal=ttypes.NullType.__NULL__) + assert ValueWrapper(null_val).cast_primitive() is None + + # Test node + node_val = ttypes.Value(vVal=self.get_vertex_value(b"Tom")) + node = ValueWrapper(node_val) + assert ValueWrapper(node_val).cast_primitive() == { + "vid": node.get_id().cast(), + "tags": node.tags(), + "props": node.properties(), + } + + # Test relationship + relationship_val = ttypes.Value(eVal=self.get_edge_value(b"Tom", b"Lily")) + edge = ValueWrapper(relationship_val) + assert ValueWrapper(relationship_val).cast_primitive() == { + "src": edge.start_vertex_id().cast(), + "dst": edge.end_vertex_id().cast(), + "type": edge.edge_name(), + "rank": edge.ranking(), + "props": edge.properties(), + } + + # Test path + path_val = ttypes.Value(pVal=self.get_path_value(b"Tom")) + path = ValueWrapper(path_val) + path_primitive = path.cast_primitive() + assert path_primitive == { + "path_str": path.__repr__(), + "start_node": _cast_node(path.start_node().cast()), + "nodes": [_cast_node(node) for node in path.nodes()], + "relationships": [ + _cast_relationship(relationship) + for relationship in path.relationships() + ], + } + + # Test geography + geography_val = ttypes.Value(ggVal=self.get_geography_value(3.0, 5.2)) + geography = ValueWrapper(geography_val) + assert geography.cast_primitive() == geography.__repr__() + + # Test duration + duration_val = ttypes.Value(duVal=Duration(86400, 3000, 12)) + duration = ValueWrapper(duration_val) + assert duration.cast_primitive() == duration.__repr__() + + # Test list + list_val = ttypes.Value() + str_val1 = ttypes.Value() + str_val1.set_sVal(b"word") + str_val2 = ttypes.Value() + str_val2.set_sVal(b"car") + val_list = NList() + val_list.values = [str_val1, str_val2] + list_val.set_lVal(val_list) + list = ValueWrapper(list_val) + assert list.cast_primitive() == [ + ValueWrapper(str_val1).cast_primitive(), + ValueWrapper(str_val2).cast_primitive(), + ] + + # Test set + set_val = ttypes.Value() + tmp_set_val = NSet() + tmp_set_val.values = set() + tmp_set_val.values.add(str_val1) + tmp_set_val.values.add(str_val2) + set_val.set_uVal(tmp_set_val) + set = ValueWrapper(set_val) + assert set.cast_primitive() == { + ValueWrapper(str_val1).cast_primitive(), + ValueWrapper(str_val2).cast_primitive(), + } + + # Test map + map_val = ttypes.Value() + tmp_map_val = NMap() + tmp_map_val.kvs = {b"a": str_val1, b"b": str_val2} + map_val.set_mVal(tmp_map_val) + map = ValueWrapper(map_val) + assert map.cast_primitive() == { + "a": ValueWrapper(str_val1).cast_primitive(), + "b": ValueWrapper(str_val2).cast_primitive(), + } + + # Test time + time_val = ttypes.Value() + time_val.set_tVal(Time(10, 10, 10, 10000)) + time = ValueWrapper(time_val) + assert time.cast_primitive() == time.__repr__() + def test_as_time(self): time = Time() time.hour = 10 From 881e9ae8abaf326e12f402ee53370b0e77f4672b Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Fri, 15 Mar 2024 18:05:12 +0000 Subject: [PATCH 05/16] chore: polishing wording of contribution --- README.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ab48ccd8..e6fbc595 100644 --- a/README.md +++ b/README.md @@ -340,35 +340,37 @@ See [ScanVertexEdgeExample.py](example/ScanVertexEdgeExample.py) for more detail | 3.4.0 | >=3.4.0 | | 3.5.0 | >=3.4.0 | | master | master | +## How to Contribute to Nebula-Python -## How to contribute to nebula-python +To contribute, start by [forking](https://github.com/vesoft-inc/nebula-python/fork) the repository. Next, clone your forked repository to your local machine. Remember to substitute `{username}` with your actual GitHub username in the URL below: -[Fork](https://github.com/vesoft-inc/nebula-python/fork) this repo, then clone it locally -(be sure to replace the `{username}` in the repo URL below with your GitHub username): -``` +```bash git clone https://github.com/{username}/nebula-python.git cd nebula-python ``` +For package management, we utilize [PDM](https://github.com/pdm-project/pdm). Please begin by installing it: -We use [PMD](https://github.com/pdm-project/pdm) to manage the package, install it first: - -``` +```bash pipx install pdm ``` Visit the [PDM documentation](https://pdm-project.org) for alternative installation methods. Install the package and all dev dependencies: -``` + +```bash pdm install ``` Make sure the Nebula server in running, then run the tests with pytest: -``` + +```bash pdm test ``` + Using the default formatter with [black](https://github.com/psf/black). Please run `pdm fmt` to format python code before submitting. See [How to contribute](https://github.com/vesoft-inc/nebula-community/blob/master/Contributors/how-to-contribute.md) for the general process of contributing to Nebula projects. + From d3f83eaa3d6eb7a45b711b2920e2eb57f489758c Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Fri, 15 Mar 2024 18:10:01 +0000 Subject: [PATCH 06/16] docs: polishing readme --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6fbc595..c7676ba1 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,15 @@ Before proceeding, it's crucial to choose the correct branch that suits your req └──nebula-python    │    ├── nebula3 // client source code -    │   ├── fbthrift // the fbthrift lib code +    │   ├── fbthrift // the RPC code generated from thrift protocol    │   ├── common    │   ├── data    │   ├── graph    │   ├── meta    │   ├── net // the net code for graph client -    │   ├── storage +    │   ├── storage // the storage client code    │   ├── Config.py // the pool config -    │   └── Exception.py // the define exception +    │   └── Exception.py // the exceptions    │    ├── examples    │   ├── FormatResp.py // the format response example @@ -340,8 +340,12 @@ See [ScanVertexEdgeExample.py](example/ScanVertexEdgeExample.py) for more detail | 3.4.0 | >=3.4.0 | | 3.5.0 | >=3.4.0 | | master | master | + ## How to Contribute to Nebula-Python +
+Click to expand + To contribute, start by [forking](https://github.com/vesoft-inc/nebula-python/fork) the repository. Next, clone your forked repository to your local machine. Remember to substitute `{username}` with your actual GitHub username in the URL below: ```bash @@ -374,3 +378,5 @@ Please run `pdm fmt` to format python code before submitting. See [How to contribute](https://github.com/vesoft-inc/nebula-community/blob/master/Contributors/how-to-contribute.md) for the general process of contributing to Nebula projects. +
+ From c4d89a3da39db22449032f72652e6cf382cfb713 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 02:09:57 +0000 Subject: [PATCH 07/16] fix: test cases missing import(sorry) --- tests/test_data_type.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_data_type.py b/tests/test_data_type.py index f02a2afc..65bf7068 100644 --- a/tests/test_data_type.py +++ b/tests/test_data_type.py @@ -11,6 +11,7 @@ from nebula3.common import ttypes from nebula3.common.ttypes import ( + Date, DateTime, Duration, Edge, @@ -20,6 +21,7 @@ NSet, NullType, Time, + Value, Vertex, ) @@ -29,6 +31,7 @@ DateWrapper, DurationWrapper, GeographyWrapper, + Node, Null, PathWrapper, Relationship, From d4acd12876233a6ffff494fa4f70c35baeaf31f3 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 02:40:50 +0000 Subject: [PATCH 08/16] fix: tag props calling failure and tests --- nebula3/data/DataObject.py | 5 +++-- tests/test_data_type.py | 36 +++++++++++++++++++----------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/nebula3/data/DataObject.py b/nebula3/data/DataObject.py index a2948dfc..df86118e 100644 --- a/nebula3/data/DataObject.py +++ b/nebula3/data/DataObject.py @@ -709,8 +709,9 @@ def cast_primitive(self) -> Any: def _cast_node(node: Vertex): return { "vid": node.get_id().cast(), - "tags": node.tags(), - "props": node.properties(), + "tags": { + tag_name: node.properties(tag_name) for tag_name in node.tags() + }, } def _cast_relationship(edge: Edge): diff --git a/tests/test_data_type.py b/tests/test_data_type.py index 65bf7068..8aad8f78 100644 --- a/tests/test_data_type.py +++ b/tests/test_data_type.py @@ -424,16 +424,15 @@ def _cast_relationship(edge: Edge): # Test node node_val = ttypes.Value(vVal=self.get_vertex_value(b"Tom")) - node = ValueWrapper(node_val) + node = ValueWrapper(node_val).as_node() assert ValueWrapper(node_val).cast_primitive() == { "vid": node.get_id().cast(), - "tags": node.tags(), - "props": node.properties(), + "tags": {tag_name: node.properties(tag_name) for tag_name in node.tags()}, } # Test relationship relationship_val = ttypes.Value(eVal=self.get_edge_value(b"Tom", b"Lily")) - edge = ValueWrapper(relationship_val) + edge = ValueWrapper(relationship_val).as_relationship() assert ValueWrapper(relationship_val).cast_primitive() == { "src": edge.start_vertex_id().cast(), "dst": edge.end_vertex_id().cast(), @@ -444,8 +443,9 @@ def _cast_relationship(edge: Edge): # Test path path_val = ttypes.Value(pVal=self.get_path_value(b"Tom")) - path = ValueWrapper(path_val) - path_primitive = path.cast_primitive() + path_raw = ValueWrapper(path_val) + path = path_raw.as_path() + path_primitive = path_raw.cast_primitive() assert path_primitive == { "path_str": path.__repr__(), "start_node": _cast_node(path.start_node().cast()), @@ -458,13 +458,15 @@ def _cast_relationship(edge: Edge): # Test geography geography_val = ttypes.Value(ggVal=self.get_geography_value(3.0, 5.2)) - geography = ValueWrapper(geography_val) - assert geography.cast_primitive() == geography.__repr__() + geography_raw = ValueWrapper(geography_val) + geography = geography_raw.as_geography() + assert geography_raw.cast_primitive() == geography.__repr__() # Test duration duration_val = ttypes.Value(duVal=Duration(86400, 3000, 12)) - duration = ValueWrapper(duration_val) - assert duration.cast_primitive() == duration.__repr__() + duration_raw = ValueWrapper(duration_val) + duration = duration_raw.as_duration() + assert duration_raw.cast_primitive() == duration.__repr__() # Test list list_val = ttypes.Value() @@ -475,8 +477,8 @@ def _cast_relationship(edge: Edge): val_list = NList() val_list.values = [str_val1, str_val2] list_val.set_lVal(val_list) - list = ValueWrapper(list_val) - assert list.cast_primitive() == [ + list_raw = ValueWrapper(list_val) + assert list_raw.cast_primitive() == [ ValueWrapper(str_val1).cast_primitive(), ValueWrapper(str_val2).cast_primitive(), ] @@ -488,8 +490,8 @@ def _cast_relationship(edge: Edge): tmp_set_val.values.add(str_val1) tmp_set_val.values.add(str_val2) set_val.set_uVal(tmp_set_val) - set = ValueWrapper(set_val) - assert set.cast_primitive() == { + set_raw = ValueWrapper(set_val) + assert set_raw.cast_primitive() == { ValueWrapper(str_val1).cast_primitive(), ValueWrapper(str_val2).cast_primitive(), } @@ -499,8 +501,8 @@ def _cast_relationship(edge: Edge): tmp_map_val = NMap() tmp_map_val.kvs = {b"a": str_val1, b"b": str_val2} map_val.set_mVal(tmp_map_val) - map = ValueWrapper(map_val) - assert map.cast_primitive() == { + map_raw = ValueWrapper(map_val) + assert map_raw.cast_primitive() == { "a": ValueWrapper(str_val1).cast_primitive(), "b": ValueWrapper(str_val2).cast_primitive(), } @@ -508,7 +510,7 @@ def _cast_relationship(edge: Edge): # Test time time_val = ttypes.Value() time_val.set_tVal(Time(10, 10, 10, 10000)) - time = ValueWrapper(time_val) + time = ValueWrapper(time_val).as_time() assert time.cast_primitive() == time.__repr__() def test_as_time(self): From 9c1f68520beda42cbc5e096fae058be2933d9395 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 02:46:55 +0000 Subject: [PATCH 09/16] fix test path primitive --- tests/test_data_type.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/test_data_type.py b/tests/test_data_type.py index 8aad8f78..87822910 100644 --- a/tests/test_data_type.py +++ b/tests/test_data_type.py @@ -448,12 +448,9 @@ def _cast_relationship(edge: Edge): path_primitive = path_raw.cast_primitive() assert path_primitive == { "path_str": path.__repr__(), - "start_node": _cast_node(path.start_node().cast()), - "nodes": [_cast_node(node) for node in path.nodes()], - "relationships": [ - _cast_relationship(relationship) - for relationship in path.relationships() - ], + "start_node": _cast_node(path.start_node()), + "edges": [_cast_relationship(x) for x in path.relationships()], + "nodes": [_cast_node(x) for x in path.nodes()], } # Test geography From 641398522dd95bbd3baa80a4815ecded5ae7322d Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 03:09:27 +0000 Subject: [PATCH 10/16] fix tests, add example tests --- README.md | 2 +- example/FormatResp.py | 4 ++-- example/GraphClientSimpleExample.py | 27 +++++++++++++++++++++++++-- tests/test_data_type.py | 10 ++++++---- 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index c7676ba1..492984d8 100644 --- a/README.md +++ b/README.md @@ -252,7 +252,7 @@ def result_to_df(result: ResultSet) -> pd.DataFrame: col_name = columns[col_num] col_list = result.column_values(col_name) d[col_name] = [x.cast() for x in col_list] - return pd.DataFrame.from_dict(d, orient='columns') + return pd.DataFrame(d) # define a config config = Config() diff --git a/example/FormatResp.py b/example/FormatResp.py index 98a059c6..87232ae2 100644 --- a/example/FormatResp.py +++ b/example/FormatResp.py @@ -19,7 +19,7 @@ # Method 0 (Recommended) # # nebula3-python>=3.6.0 # ################################ -def result_to_df(result: ResultSet) -> pd.DataFrame: +def result_to_df_buildin(result: ResultSet) -> pd.DataFrame: """ build list for each column, and transform to dataframe """ @@ -42,7 +42,7 @@ def result_to_df(result: ResultSet) -> pd.DataFrame: col_name = columns[col_num] col_list = result.column_values(col_name) d[col_name] = [x.cast() for x in col_list] - return pd.DataFrame.from_dict(d, columns=columns) + return pd.DataFrame(d) ################################ diff --git a/example/GraphClientSimpleExample.py b/example/GraphClientSimpleExample.py index 5e7457c4..94f891a3 100644 --- a/example/GraphClientSimpleExample.py +++ b/example/GraphClientSimpleExample.py @@ -9,7 +9,8 @@ import json import time -from FormatResp import print_resp +from FormatResp import print_resp, result_to_df_buildin, result_to_df +import pandas as pd from nebula3.Config import Config from nebula3.gclient.net import ConnectionPool @@ -35,7 +36,7 @@ client.execute( "CREATE SPACE IF NOT EXISTS test(vid_type=FIXED_STRING(30)); USE test;" "CREATE TAG IF NOT EXISTS person(name string, age int);" - "CREATE EDGE like (likeness double);" + "CREATE EDGE IF NOT EXISTS like (likeness double);" ) # insert data need to sleep after create schema @@ -59,6 +60,28 @@ assert resp.is_succeeded(), resp.error_msg() print_resp(resp) + # query data + resp = client.execute( + 'GET SUBGRAPH WITH PROP 2 STEPS FROM "Bob" YIELD VERTICES AS nodes, EDGES AS relationships;' + ) + df = result_to_df_buildin(resp) + df_1 = result_to_df(resp) + + print("Testing pandas dataframe operations") + print(df_1) + + # Convert the dataframe 'df' into a CSV file + df.to_csv('subgraph_data.csv', index=False) + print("Dataframe 'df' has been exported to 'subgraph_data.csv'.") + + # Read the CSV file back into a dataframe + df_csv = pd.read_csv('subgraph_data.csv') + print("CSV file 'subgraph_data.csv' has been read into dataframe 'df_csv'.") + + # Display the first 5 rows of the dataframe + print("Displaying the first 5 rows of dataframe 'df_csv':") + print(df_csv.head()) + # drop space resp = client.execute("DROP SPACE test") assert resp.is_succeeded(), resp.error_msg() diff --git a/tests/test_data_type.py b/tests/test_data_type.py index 87822910..9e3134e2 100644 --- a/tests/test_data_type.py +++ b/tests/test_data_type.py @@ -389,8 +389,9 @@ def test_cast_primitive(self): def _cast_node(node: Vertex): return { "vid": node.get_id().cast(), - "tags": node.tags(), - "props": node.properties(), + "tags": { + tag_name: node.properties(tag_name) for tag_name in node.tags() + }, } def _cast_relationship(edge: Edge): @@ -507,8 +508,9 @@ def _cast_relationship(edge: Edge): # Test time time_val = ttypes.Value() time_val.set_tVal(Time(10, 10, 10, 10000)) - time = ValueWrapper(time_val).as_time() - assert time.cast_primitive() == time.__repr__() + time_raw = ValueWrapper(time_val) + time = time_raw.as_time() + assert time_raw.cast_primitive() == time.__repr__() def test_as_time(self): time = Time() From 3856af5127fb78f2456cf75bdd7637137d0b3119 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 03:23:54 +0000 Subject: [PATCH 11/16] fix time.cast_primitive() --- tests/test_data_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_data_type.py b/tests/test_data_type.py index 9e3134e2..1e3dc321 100644 --- a/tests/test_data_type.py +++ b/tests/test_data_type.py @@ -510,7 +510,7 @@ def _cast_relationship(edge: Edge): time_val.set_tVal(Time(10, 10, 10, 10000)) time_raw = ValueWrapper(time_val) time = time_raw.as_time() - assert time_raw.cast_primitive() == time.__repr__() + assert time_raw.cast_primitive() == time.get_local_time_str() def test_as_time(self): time = Time() From 54f0237978711ca2e153bcfafbb342b3b4aaab8b Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 04:07:53 +0000 Subject: [PATCH 12/16] docs: polishing Getting Started in a straightforward way --- README.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 492984d8..2a365339 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,4 @@ -# nebula-python - -This repository contains the official Python API for NebulaGraph. +# NebulaGraph Python Client [![pdm-managed](https://img.shields.io/badge/pdm-managed-blueviolet)](https://pdm.fming.dev) [![pypi-version](https://img.shields.io/pypi/v/nebula3-python)](https://pypi.org/project/nebula3-python/) @@ -8,11 +6,33 @@ This repository contains the official Python API for NebulaGraph. ## Getting Started -Before proceeding, it's crucial to choose the correct branch that suits your requirements. To ascertain the compatibility between the API and NebulaGraph Database, refer to the [Capability Matrix](#Capability-Matrix) section. It's worth noting that the master branch is currently tailored for NebulaGraph 3.x. +**Note**: Ensure you are using the correct version, refer to the [Capability Matrix](#Capability-Matrix) for how the Python client version corresponds to the NebulaGraph Database version. + +### Accessing NebulaGraph + +- For **first-time** trying out Python client, go through [Quick Example: Connecting to GraphD Using Graph Client](#Quick-Example:-Connecting-to-GraphD-Using-Graph-Client). + +- If your Graph Application is a **Web Service** dedicated to one Graph Space, go with Singleton of **Session Pool**, check [Using the Session Pool: A Guide](#Using-the-Session-Pool:-A-Guide). + +- If you're building Graph Analysis Tools(Scan instead of Query), you may want to use the **Storage Client** to scan vertices and edges, see [Quick Example: Using Storage Client to Scan Vertices and Edges](#Quick-Example:-Using-Storage-Client-to-Scan-Vertices-and-Edges). + +### Handling Query Results + +- On how to form a query result into a **Pandas DataFrame**, see [Example: Fetching Query Results into a Pandas DataFrame](#Example:-Fetching-Query-Results-into-a-Pandas-DataFrame). + +- On how to render/visualize the query result, see [Example: Extracting Edge and Vertex Lists from Query Results](#Example:-Extracting-Edge-and-Vertex-Lists-from-Query-Results), it demonstrates how to extract lists of edges and vertices from any query result by utilizing the `ResultSet.dict_for_vis()` method. + +### Jupyter Notebook Integration + +[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/wey-gu/ipython-ngql/blob/main/examples/get_started.ipynb) + + +If you are about to access NebulaGraph within Jupyter Notebook, you may want to use the [NebulaGraph Jupyter Extension](https://pypi.org/project/ipython-ngql/), which provides a more interactive way to access NebulaGraph. See also this on Google Colab: [NebulaGraph on Google Colab](https://colab.research.google.com/github/wey-gu/ipython-ngql/blob/main/examples/get_started.ipynb). ## Directory Structure Overview ```text +. └──nebula-python    │    ├── nebula3 // client source code From 9f6bc54b8a68187b52c6447ca34b7269ff99079b Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 04:25:00 +0000 Subject: [PATCH 13/16] feat: Config and SessionPoolConfig could be omit by default - Readme getting started polished - Config should be able to omit - The previous SessionPoolConfig Check mechnism was wrong, fixed it --- README.md | 68 ++++++++++++++------------- nebula3/gclient/net/ConnectionPool.py | 11 +++-- nebula3/gclient/net/SessionPool.py | 8 +++- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 2a365339..6dc1398e 100644 --- a/README.md +++ b/README.md @@ -29,38 +29,6 @@ If you are about to access NebulaGraph within Jupyter Notebook, you may want to use the [NebulaGraph Jupyter Extension](https://pypi.org/project/ipython-ngql/), which provides a more interactive way to access NebulaGraph. See also this on Google Colab: [NebulaGraph on Google Colab](https://colab.research.google.com/github/wey-gu/ipython-ngql/blob/main/examples/get_started.ipynb). -## Directory Structure Overview - -```text -. -└──nebula-python -    │ -    ├── nebula3 // client source code -    │   ├── fbthrift // the RPC code generated from thrift protocol -    │   ├── common -    │   ├── data -    │   ├── graph -    │   ├── meta -    │   ├── net // the net code for graph client -    │   ├── storage // the storage client code -    │   ├── Config.py // the pool config -    │   └── Exception.py // the exceptions -    │ -    ├── examples -    │   ├── FormatResp.py // the format response example -    │   ├── SessionPoolExample.py // the session pool example -    │   ├── GraphClientMultiThreadExample.py // the multi thread example -    │   ├── GraphClientSimpleExample.py // the simple example -    │   └── ScanVertexEdgeExample.py // the scan vertex and edge example(storage client) -    │ -    ├── tests // the test code -    │ -    ├── setup.py // used to install or package - │ - └── README.md // the introduction of nebula3-python - -``` - ## Obtaining nebula3-python ### Method 1: Installation via pip @@ -361,7 +329,41 @@ See [ScanVertexEdgeExample.py](example/ScanVertexEdgeExample.py) for more detail | 3.5.0 | >=3.4.0 | | master | master | -## How to Contribute to Nebula-Python + +## Directory Structure Overview + +```text +. +└──nebula-python +    │ +    ├── nebula3 // client source code +    │   ├── fbthrift // the RPC code generated from thrift protocol +    │   ├── common +    │   ├── data +    │   ├── graph +    │   ├── meta +    │   ├── net // the net code for graph client +    │   ├── storage // the storage client code +    │   ├── Config.py // the pool config +    │   └── Exception.py // the exceptions +    │ +    ├── examples +    │   ├── FormatResp.py // the format response example +    │   ├── SessionPoolExample.py // the session pool example +    │   ├── GraphClientMultiThreadExample.py // the multi thread example +    │   ├── GraphClientSimpleExample.py // the simple example +    │   └── ScanVertexEdgeExample.py // the scan vertex and edge example(storage client) +    │ +    ├── tests // the test code +    │ +    ├── setup.py // used to install or package + │ + └── README.md // the introduction of nebula3-python + +``` + + +## Contribute to Nebula-Python
Click to expand diff --git a/nebula3/gclient/net/ConnectionPool.py b/nebula3/gclient/net/ConnectionPool.py index 134e4be7..a8bcda4a 100644 --- a/nebula3/gclient/net/ConnectionPool.py +++ b/nebula3/gclient/net/ConnectionPool.py @@ -41,7 +41,7 @@ def __init__(self): def __del__(self): self.close() - def init(self, addresses, configs, ssl_conf=None): + def init(self, addresses, configs=None, ssl_conf=None): """init the connection pool :param addresses: the graphd servers' addresses @@ -52,8 +52,13 @@ def init(self, addresses, configs, ssl_conf=None): if self._close: logger.error('The pool has init or closed.') raise RuntimeError('The pool has init or closed.') - assert isinstance(configs, Config) - self._configs = configs + if configs is None: + self._configs = Config() + else: + assert isinstance( + configs, Config + ), 'wrong type of Config, try this: `from nebula3.Config import Config`' + self._configs = configs self._ssl_configs = ssl_conf for address in addresses: if address not in self._addresses: diff --git a/nebula3/gclient/net/SessionPool.py b/nebula3/gclient/net/SessionPool.py index 652f7334..827d400e 100644 --- a/nebula3/gclient/net/SessionPool.py +++ b/nebula3/gclient/net/SessionPool.py @@ -70,7 +70,7 @@ def __init__(self, username, password, space_name, addresses): def __del__(self): self.close() - def init(self, configs): + def init(self, configs=None): """init the session pool :param username: the username of the session @@ -81,6 +81,11 @@ def init(self, configs): :return: if all addresses are valid, return True else return False. """ + if configs is not None: + assert isinstance( + configs, SessionPoolConfig + ), 'wrong type of SessionPoolConfig, try this: `from nebula3.Config import SessionPoolConfig`' + self._configs = configs # check configs try: self._check_configs() @@ -91,7 +96,6 @@ def init(self, configs): if self._close: logger.error('The pool has init or closed.') raise RuntimeError('The pool has init or closed.') - self._configs = configs # ping all servers self.update_servers_status() From 8ce2af023597e30c3e38595b002b83178c76d721 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 05:43:24 +0000 Subject: [PATCH 14/16] docs: graph vis with dict_for_vis & Apache ECharts --- README.md | 4 +- example/GraphVis.py | 53 + example/apache_echarts.html | 2401 +++++++++++++++++++++++++++++++++++ example/data.json | 2264 +++++++++++++++++++++++++++++++++ nebula3/data/ResultSet.py | 6 +- 5 files changed, 4724 insertions(+), 4 deletions(-) create mode 100644 example/GraphVis.py create mode 100644 example/apache_echarts.html create mode 100644 example/data.json diff --git a/README.md b/README.md index 6dc1398e..f7259c46 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,9 @@ result = session.execute( data_for_vis = result.dict_for_vis() ``` -And it looks like this: +Then, we could pass the `data_for_vis` to a front-end visualization library such as `vis.js`, `d3.js` or Apache ECharts. There is an example of Apache ECharts in [exapmple/apache_echarts.html](example/apache_echarts.html). + +The dict/JSON structure with `dict_for_vis()` is as follows:
Click to expand diff --git a/example/GraphVis.py b/example/GraphVis.py new file mode 100644 index 00000000..41e87dd1 --- /dev/null +++ b/example/GraphVis.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# --coding:utf-8-- + +# Copyright (c) 2024 vesoft inc. All rights reserved. +# +# This source code is licensed under Apache 2.0 License. + + +import json +import time + + +from nebula3.gclient.net import ConnectionPool + +if __name__ == "__main__": + client = None + try: + # init connection pool + connection_pool = ConnectionPool() + assert connection_pool.init([("127.0.0.1", 9669)]) + + # get session from the pool + client = connection_pool.get_session("root", "nebula") + assert client is not None + + client.execute("USE nba") + + result = client.execute( + 'GET SUBGRAPH WITH PROP 2 STEPS FROM "player101" YIELD VERTICES AS nodes, EDGES AS relationships;' + ) + + assert result.is_succeeded(), result.error_msg() + + data = result.dict_for_vis() + + json_data = json.dumps(data, indent=2, sort_keys=True) + + # save the json data to a file + with open('data.json', 'w') as f: + f.write(json_data) + + # Check the data.json file to see the result + + # See example/apache_echarts.html to see a reference implementation of the visualization + # using Apache ECharts + + except Exception: + import traceback + + print(traceback.format_exc()) + if client is not None: + client.release() + exit(1) diff --git a/example/apache_echarts.html b/example/apache_echarts.html new file mode 100644 index 00000000..0e599993 --- /dev/null +++ b/example/apache_echarts.html @@ -0,0 +1,2401 @@ + + + + + + + +
+ + + + + + + + + + + + + \ No newline at end of file diff --git a/example/data.json b/example/data.json new file mode 100644 index 00000000..c9205b84 --- /dev/null +++ b/example/data.json @@ -0,0 +1,2264 @@ +{ + "edges": [ + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "1999" + }, + "src": "player101" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2018" + }, + "src": "player101" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player101" + }, + { + "dst": "player102", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player101" + }, + { + "dst": "player125", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player101" + }, + { + "dst": "player101", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player100" + }, + { + "dst": "player101", + "name": "follow", + "props": { + "degree": "75" + }, + "src": "player102" + }, + { + "dst": "player101", + "name": "follow", + "props": { + "degree": "50" + }, + "src": "player104" + }, + { + "dst": "player101", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player108" + }, + { + "dst": "player101", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2011", + "start_year": "2003" + }, + "src": "player111" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2011", + "start_year": "2005" + }, + "src": "player121" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2017" + }, + "src": "player146" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2016" + }, + "src": "player113" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player104", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player105", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player106", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player116", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player118", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player119", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player120", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player121", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "player125", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "1997" + }, + "src": "player100" + }, + { + "dst": "player125", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player100" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "70" + }, + "src": "player105" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player107" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player109" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player144" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2012" + }, + "src": "player108" + }, + { + "dst": "team206", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2016" + }, + "src": "player108" + }, + { + "dst": "team214", + "name": "serve", + "props": { + "end_year": "2008", + "start_year": "2005" + }, + "src": "player108" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2012", + "start_year": "2008" + }, + "src": "player108" + }, + { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2005", + "start_year": "2003" + }, + "src": "player108" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player108" + }, + { + "dst": "team203", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2006" + }, + "src": "player102" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2015" + }, + "src": "player102" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "75" + }, + "src": "player102" + }, + { + "dst": "player102", + "name": "follow", + "props": { + "degree": "70" + }, + "src": "player103" + }, + { + "dst": "player102", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player135" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2017" + }, + "src": "player103" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2010" + }, + "src": "player105" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2014" + }, + "src": "player106" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2013" + }, + "src": "player107" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2010" + }, + "src": "player109" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2011" + }, + "src": "player110" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2015" + }, + "src": "player111" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2015" + }, + "src": "player112" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2013", + "start_year": "2013" + }, + "src": "player114" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2016" + }, + "src": "player138" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2002" + }, + "src": "player125" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player125" + }, + { + "dst": "player125", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player109" + }, + { + "dst": "team200", + "name": "serve", + "props": { + "end_year": "2009", + "start_year": "2007" + }, + "src": "player104" + }, + { + "dst": "team208", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2015" + }, + "src": "player104" + }, + { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2010", + "start_year": "2009" + }, + "src": "player104" + }, + { + "dst": "team219", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2018" + }, + "src": "player104" + }, + { + "dst": "team221", + "name": "serve", + "props": { + "end_year": "2013", + "start_year": "2012" + }, + "src": "player104" + }, + { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2017" + }, + "src": "player104" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2012", + "rank": 20102012, + "start_year": "2010" + }, + "src": "player104" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "rank": 20132015, + "start_year": "2013" + }, + "src": "player104" + }, + { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2017", + "rank": 20162017, + "start_year": "2016" + }, + "src": "player104" + }, + { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "rank": 20182019, + "start_year": "2018" + }, + "src": "player104" + }, + { + "dst": "player100", + "name": "follow", + "props": { + "degree": "55" + }, + "src": "player104" + }, + { + "dst": "player105", + "name": "follow", + "props": { + "degree": "60" + }, + "src": "player104" + }, + { + "dst": "player104", + "name": "follow", + "props": { + "degree": "83" + }, + "src": "player105" + }, + { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2016" + }, + "src": "player146" + }, + { + "dst": "team203", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2012" + }, + "src": "player135" + }, + { + "dst": "player116", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player121" + }, + { + "dst": "team208", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2013" + }, + "src": "player103" + }, + { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2013", + "start_year": "2013" + }, + "src": "player103" + }, + { + "dst": "player118", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player120" + }, + { + "dst": "team221", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2014" + }, + "src": "player138" + }, + { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2000", + "start_year": "1997" + }, + "src": "player114" + }, + { + "dst": "player103", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player114" + }, + { + "dst": "team200", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2016" + }, + "src": "player119" + }, + { + "dst": "team214", + "name": "serve", + "props": { + "end_year": "2009", + "start_year": "2008" + }, + "src": "player144" + }, + { + "dst": "team219", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2019" + }, + "src": "player112" + }, + { + "dst": "team200", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2016" + }, + "src": "player111" + }, + { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2018" + }, + "src": "player105" + }, + { + "dst": "player116", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player105" + }, + { + "dst": "player120", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player118" + }, + { + "dst": "team219", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2017" + }, + "src": "player109" + }, + { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2015" + }, + "src": "player109" + }, + { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2015" + }, + "src": "player110" + } + ], + "edges_count": 86, + "edges_dict": { + "('player100', 'player101', 0, 'follow')": { + "dst": "player101", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player100" + }, + "('player100', 'player125', 0, 'follow')": { + "dst": "player125", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player100" + }, + "('player100', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "1997" + }, + "src": "player100" + }, + "('player101', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player101" + }, + "('player101', 'player102', 0, 'follow')": { + "dst": "player102", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player101" + }, + "('player101', 'player125', 0, 'follow')": { + "dst": "player125", + "name": "follow", + "props": { + "degree": "95" + }, + "src": "player101" + }, + "('player101', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "1999" + }, + "src": "player101" + }, + "('player101', 'team215', 0, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2018" + }, + "src": "player101" + }, + "('player102', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "75" + }, + "src": "player102" + }, + "('player102', 'player101', 0, 'follow')": { + "dst": "player101", + "name": "follow", + "props": { + "degree": "75" + }, + "src": "player102" + }, + "('player102', 'team203', 0, 'serve')": { + "dst": "team203", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2006" + }, + "src": "player102" + }, + "('player102', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2015" + }, + "src": "player102" + }, + "('player103', 'player102', 0, 'follow')": { + "dst": "player102", + "name": "follow", + "props": { + "degree": "70" + }, + "src": "player103" + }, + "('player103', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2017" + }, + "src": "player103" + }, + "('player103', 'team208', 0, 'serve')": { + "dst": "team208", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2013" + }, + "src": "player103" + }, + "('player103', 'team218', 0, 'serve')": { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2013", + "start_year": "2013" + }, + "src": "player103" + }, + "('player104', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "55" + }, + "src": "player104" + }, + "('player104', 'player101', 0, 'follow')": { + "dst": "player101", + "name": "follow", + "props": { + "degree": "50" + }, + "src": "player104" + }, + "('player104', 'player105', 0, 'follow')": { + "dst": "player105", + "name": "follow", + "props": { + "degree": "60" + }, + "src": "player104" + }, + "('player104', 'team200', 0, 'serve')": { + "dst": "team200", + "name": "serve", + "props": { + "end_year": "2009", + "start_year": "2007" + }, + "src": "player104" + }, + "('player104', 'team204', 20132015, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "rank": 20132015, + "start_year": "2013" + }, + "src": "player104" + }, + "('player104', 'team204', 20182019, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "rank": 20182019, + "start_year": "2018" + }, + "src": "player104" + }, + "('player104', 'team208', 0, 'serve')": { + "dst": "team208", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2015" + }, + "src": "player104" + }, + "('player104', 'team215', 20102012, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2012", + "rank": 20102012, + "start_year": "2010" + }, + "src": "player104" + }, + "('player104', 'team215', 20162017, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2017", + "rank": 20162017, + "start_year": "2016" + }, + "src": "player104" + }, + "('player104', 'team218', 0, 'serve')": { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2010", + "start_year": "2009" + }, + "src": "player104" + }, + "('player104', 'team219', 0, 'serve')": { + "dst": "team219", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2018" + }, + "src": "player104" + }, + "('player104', 'team221', 0, 'serve')": { + "dst": "team221", + "name": "serve", + "props": { + "end_year": "2013", + "start_year": "2012" + }, + "src": "player104" + }, + "('player104', 'team222', 0, 'serve')": { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2017" + }, + "src": "player104" + }, + "('player105', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "70" + }, + "src": "player105" + }, + "('player105', 'player104', 0, 'follow')": { + "dst": "player104", + "name": "follow", + "props": { + "degree": "83" + }, + "src": "player105" + }, + "('player105', 'player116', 0, 'follow')": { + "dst": "player116", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player105" + }, + "('player105', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2010" + }, + "src": "player105" + }, + "('player105', 'team218', 0, 'serve')": { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2018" + }, + "src": "player105" + }, + "('player106', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2014" + }, + "src": "player106" + }, + "('player107', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player107" + }, + "('player107', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2013" + }, + "src": "player107" + }, + "('player108', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player108" + }, + "('player108', 'player101', 0, 'follow')": { + "dst": "player101", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player108" + }, + "('player108', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2012" + }, + "src": "player108" + }, + "('player108', 'team206', 0, 'serve')": { + "dst": "team206", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2016" + }, + "src": "player108" + }, + "('player108', 'team214', 0, 'serve')": { + "dst": "team214", + "name": "serve", + "props": { + "end_year": "2008", + "start_year": "2005" + }, + "src": "player108" + }, + "('player108', 'team215', 0, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2012", + "start_year": "2008" + }, + "src": "player108" + }, + "('player108', 'team222', 0, 'serve')": { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2005", + "start_year": "2003" + }, + "src": "player108" + }, + "('player109', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player109" + }, + "('player109', 'player125', 0, 'follow')": { + "dst": "player125", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player109" + }, + "('player109', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2010" + }, + "src": "player109" + }, + "('player109', 'team219', 0, 'serve')": { + "dst": "team219", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2017" + }, + "src": "player109" + }, + "('player109', 'team222', 0, 'serve')": { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2015" + }, + "src": "player109" + }, + "('player110', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2015", + "start_year": "2011" + }, + "src": "player110" + }, + "('player110', 'team218', 0, 'serve')": { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2015" + }, + "src": "player110" + }, + "('player111', 'team200', 0, 'serve')": { + "dst": "team200", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2016" + }, + "src": "player111" + }, + "('player111', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2015" + }, + "src": "player111" + }, + "('player111', 'team215', 0, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2011", + "start_year": "2003" + }, + "src": "player111" + }, + "('player112', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2015" + }, + "src": "player112" + }, + "('player112', 'team219', 0, 'serve')": { + "dst": "team219", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2019" + }, + "src": "player112" + }, + "('player113', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player101', 0, 'follow')": { + "dst": "player101", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player104', 0, 'follow')": { + "dst": "player104", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player105', 0, 'follow')": { + "dst": "player105", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player106', 0, 'follow')": { + "dst": "player106", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player116', 0, 'follow')": { + "dst": "player116", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player118', 0, 'follow')": { + "dst": "player118", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player119', 0, 'follow')": { + "dst": "player119", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player120', 0, 'follow')": { + "dst": "player120", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player121', 0, 'follow')": { + "dst": "player121", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'player125', 0, 'follow')": { + "dst": "player125", + "name": "follow", + "props": { + "degree": "99" + }, + "src": "player113" + }, + "('player113', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2016" + }, + "src": "player113" + }, + "('player114', 'player103', 0, 'follow')": { + "dst": "player103", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player114" + }, + "('player114', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2013", + "start_year": "2013" + }, + "src": "player114" + }, + "('player114', 'team218', 0, 'serve')": { + "dst": "team218", + "name": "serve", + "props": { + "end_year": "2000", + "start_year": "1997" + }, + "src": "player114" + }, + "('player118', 'player120', 0, 'follow')": { + "dst": "player120", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player118" + }, + "('player119', 'team200', 0, 'serve')": { + "dst": "team200", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2016" + }, + "src": "player119" + }, + "('player120', 'player118', 0, 'follow')": { + "dst": "player118", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player120" + }, + "('player121', 'player116', 0, 'follow')": { + "dst": "player116", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player121" + }, + "('player121', 'team215', 0, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2011", + "start_year": "2005" + }, + "src": "player121" + }, + "('player125', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "90" + }, + "src": "player125" + }, + "('player125', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2002" + }, + "src": "player125" + }, + "('player135', 'player102', 0, 'follow')": { + "dst": "player102", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player135" + }, + "('player135', 'team203', 0, 'serve')": { + "dst": "team203", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2012" + }, + "src": "player135" + }, + "('player138', 'team204', 0, 'serve')": { + "dst": "team204", + "name": "serve", + "props": { + "end_year": "2019", + "start_year": "2016" + }, + "src": "player138" + }, + "('player138', 'team221', 0, 'serve')": { + "dst": "team221", + "name": "serve", + "props": { + "end_year": "2016", + "start_year": "2014" + }, + "src": "player138" + }, + "('player144', 'player100', 0, 'follow')": { + "dst": "player100", + "name": "follow", + "props": { + "degree": "80" + }, + "src": "player144" + }, + "('player144', 'team214', 0, 'serve')": { + "dst": "team214", + "name": "serve", + "props": { + "end_year": "2009", + "start_year": "2008" + }, + "src": "player144" + }, + "('player146', 'team215', 0, 'serve')": { + "dst": "team215", + "name": "serve", + "props": { + "end_year": "2018", + "start_year": "2017" + }, + "src": "player146" + }, + "('player146', 'team222', 0, 'serve')": { + "dst": "team222", + "name": "serve", + "props": { + "end_year": "2017", + "start_year": "2016" + }, + "src": "player146" + } + }, + "nodes": [ + { + "id": "player101", + "labels": [ + "player" + ], + "props": { + "age": "36", + "id": "player101", + "name": "Tony Parker" + } + }, + { + "id": "team215", + "labels": [ + "team" + ], + "props": { + "id": "team215", + "name": "Hornets" + } + }, + { + "id": "player113", + "labels": [ + "player" + ], + "props": { + "age": "29", + "id": "player113", + "name": "Dejounte Murray" + } + }, + { + "id": "player100", + "labels": [ + "player" + ], + "props": { + "age": "42", + "id": "player100", + "name": "Tim Duncan" + } + }, + { + "id": "player108", + "labels": [ + "player" + ], + "props": { + "age": "36", + "id": "player108", + "name": "Boris Diaw" + } + }, + { + "id": "player102", + "labels": [ + "player" + ], + "props": { + "age": "33", + "id": "player102", + "name": "LaMarcus Aldridge" + } + }, + { + "id": "team204", + "labels": [ + "team" + ], + "props": { + "id": "team204", + "name": "Spurs" + } + }, + { + "id": "player125", + "labels": [ + "player" + ], + "props": { + "age": "41", + "id": "player125", + "name": "Manu Ginobili" + } + }, + { + "id": "player104", + "labels": [ + "player" + ], + "props": { + "age": "32", + "id": "player104", + "name": "Marco Belinelli" + } + }, + { + "id": "player146", + "labels": [ + "player" + ], + "props": { + "age": "33", + "id": "player146", + "name": "Dwight Howard" + } + }, + { + "id": "team206", + "labels": [ + "team" + ], + "props": { + "id": "team206", + "name": "Jazz" + } + }, + { + "id": "player135", + "labels": [ + "player" + ], + "props": { + "age": "28", + "id": "player135", + "name": "Damian Lillard" + } + }, + { + "id": "team219", + "labels": [ + "team" + ], + "props": { + "id": "team219", + "name": "76ers" + } + }, + { + "id": "player121", + "labels": [ + "player" + ], + "props": { + "age": "33", + "id": "player121", + "name": "Chris Paul" + } + }, + { + "id": "player103", + "labels": [ + "player" + ], + "props": { + "age": "32", + "id": "player103", + "name": "Rudy Gay" + } + }, + { + "id": "team214", + "labels": [ + "team" + ], + "props": { + "id": "team214", + "name": "Suns" + } + }, + { + "id": "player116", + "labels": [ + "player" + ], + "props": { + "age": "34", + "id": "player116", + "name": "LeBron James" + } + }, + { + "id": "player120", + "labels": [ + "player" + ], + "props": { + "age": "29", + "id": "player120", + "name": "James Harden" + } + }, + { + "id": "player107", + "labels": [ + "player" + ], + "props": { + "age": "32", + "id": "player107", + "name": "Aron Baynes" + } + }, + { + "id": "team203", + "labels": [ + "team" + ], + "props": { + "id": "team203", + "name": "Trail Blazers" + } + }, + { + "id": "team200", + "labels": [ + "team" + ], + "props": { + "id": "team200", + "name": "Warriors" + } + }, + { + "id": "player106", + "labels": [ + "player" + ], + "props": { + "age": "25", + "id": "player106", + "name": "Kyle Anderson" + } + }, + { + "id": "player138", + "labels": [ + "player" + ], + "props": { + "age": "38", + "id": "player138", + "name": "Paul Gasol" + } + }, + { + "id": "player114", + "labels": [ + "player" + ], + "props": { + "age": "39", + "id": "player114", + "name": "Tracy McGrady" + } + }, + { + "id": "team218", + "labels": [ + "team" + ], + "props": { + "id": "team218", + "name": "Raptors" + } + }, + { + "id": "player119", + "labels": [ + "player" + ], + "props": { + "age": "30", + "id": "player119", + "name": "Kevin Durant" + } + }, + { + "id": "player144", + "labels": [ + "player" + ], + "props": { + "age": "47", + "id": "player144", + "name": "Shaquille O'Neal" + } + }, + { + "id": "player112", + "labels": [ + "player" + ], + "props": { + "age": "29", + "id": "player112", + "name": "Jonathon Simmons" + } + }, + { + "id": "player111", + "labels": [ + "player" + ], + "props": { + "age": "38", + "id": "player111", + "name": "David West" + } + }, + { + "id": "player105", + "labels": [ + "player" + ], + "props": { + "age": "31", + "id": "player105", + "name": "Danny Green" + } + }, + { + "id": "player118", + "labels": [ + "player" + ], + "props": { + "age": "30", + "id": "player118", + "name": "Russell Westbrook" + } + }, + { + "id": "player109", + "labels": [ + "player" + ], + "props": { + "age": "34", + "id": "player109", + "name": "Tiago Splitter" + } + }, + { + "id": "team222", + "labels": [ + "team" + ], + "props": { + "id": "team222", + "name": "Hawks" + } + }, + { + "id": "team221", + "labels": [ + "team" + ], + "props": { + "id": "team221", + "name": "Bulls" + } + }, + { + "id": "player110", + "labels": [ + "player" + ], + "props": { + "age": "27", + "id": "player110", + "name": "Cory Joseph" + } + }, + { + "id": "team208", + "labels": [ + "team" + ], + "props": { + "id": "team208", + "name": "Kings" + } + } + ], + "nodes_count": 36, + "nodes_dict": { + "player100": { + "id": "player100", + "labels": [ + "player" + ], + "props": { + "age": "42", + "id": "player100", + "name": "Tim Duncan" + } + }, + "player101": { + "id": "player101", + "labels": [ + "player" + ], + "props": { + "age": "36", + "id": "player101", + "name": "Tony Parker" + } + }, + "player102": { + "id": "player102", + "labels": [ + "player" + ], + "props": { + "age": "33", + "id": "player102", + "name": "LaMarcus Aldridge" + } + }, + "player103": { + "id": "player103", + "labels": [ + "player" + ], + "props": { + "age": "32", + "id": "player103", + "name": "Rudy Gay" + } + }, + "player104": { + "id": "player104", + "labels": [ + "player" + ], + "props": { + "age": "32", + "id": "player104", + "name": "Marco Belinelli" + } + }, + "player105": { + "id": "player105", + "labels": [ + "player" + ], + "props": { + "age": "31", + "id": "player105", + "name": "Danny Green" + } + }, + "player106": { + "id": "player106", + "labels": [ + "player" + ], + "props": { + "age": "25", + "id": "player106", + "name": "Kyle Anderson" + } + }, + "player107": { + "id": "player107", + "labels": [ + "player" + ], + "props": { + "age": "32", + "id": "player107", + "name": "Aron Baynes" + } + }, + "player108": { + "id": "player108", + "labels": [ + "player" + ], + "props": { + "age": "36", + "id": "player108", + "name": "Boris Diaw" + } + }, + "player109": { + "id": "player109", + "labels": [ + "player" + ], + "props": { + "age": "34", + "id": "player109", + "name": "Tiago Splitter" + } + }, + "player110": { + "id": "player110", + "labels": [ + "player" + ], + "props": { + "age": "27", + "id": "player110", + "name": "Cory Joseph" + } + }, + "player111": { + "id": "player111", + "labels": [ + "player" + ], + "props": { + "age": "38", + "id": "player111", + "name": "David West" + } + }, + "player112": { + "id": "player112", + "labels": [ + "player" + ], + "props": { + "age": "29", + "id": "player112", + "name": "Jonathon Simmons" + } + }, + "player113": { + "id": "player113", + "labels": [ + "player" + ], + "props": { + "age": "29", + "id": "player113", + "name": "Dejounte Murray" + } + }, + "player114": { + "id": "player114", + "labels": [ + "player" + ], + "props": { + "age": "39", + "id": "player114", + "name": "Tracy McGrady" + } + }, + "player116": { + "id": "player116", + "labels": [ + "player" + ], + "props": { + "age": "34", + "id": "player116", + "name": "LeBron James" + } + }, + "player118": { + "id": "player118", + "labels": [ + "player" + ], + "props": { + "age": "30", + "id": "player118", + "name": "Russell Westbrook" + } + }, + "player119": { + "id": "player119", + "labels": [ + "player" + ], + "props": { + "age": "30", + "id": "player119", + "name": "Kevin Durant" + } + }, + "player120": { + "id": "player120", + "labels": [ + "player" + ], + "props": { + "age": "29", + "id": "player120", + "name": "James Harden" + } + }, + "player121": { + "id": "player121", + "labels": [ + "player" + ], + "props": { + "age": "33", + "id": "player121", + "name": "Chris Paul" + } + }, + "player125": { + "id": "player125", + "labels": [ + "player" + ], + "props": { + "age": "41", + "id": "player125", + "name": "Manu Ginobili" + } + }, + "player135": { + "id": "player135", + "labels": [ + "player" + ], + "props": { + "age": "28", + "id": "player135", + "name": "Damian Lillard" + } + }, + "player138": { + "id": "player138", + "labels": [ + "player" + ], + "props": { + "age": "38", + "id": "player138", + "name": "Paul Gasol" + } + }, + "player144": { + "id": "player144", + "labels": [ + "player" + ], + "props": { + "age": "47", + "id": "player144", + "name": "Shaquille O'Neal" + } + }, + "player146": { + "id": "player146", + "labels": [ + "player" + ], + "props": { + "age": "33", + "id": "player146", + "name": "Dwight Howard" + } + }, + "team200": { + "id": "team200", + "labels": [ + "team" + ], + "props": { + "id": "team200", + "name": "Warriors" + } + }, + "team203": { + "id": "team203", + "labels": [ + "team" + ], + "props": { + "id": "team203", + "name": "Trail Blazers" + } + }, + "team204": { + "id": "team204", + "labels": [ + "team" + ], + "props": { + "id": "team204", + "name": "Spurs" + } + }, + "team206": { + "id": "team206", + "labels": [ + "team" + ], + "props": { + "id": "team206", + "name": "Jazz" + } + }, + "team208": { + "id": "team208", + "labels": [ + "team" + ], + "props": { + "id": "team208", + "name": "Kings" + } + }, + "team214": { + "id": "team214", + "labels": [ + "team" + ], + "props": { + "id": "team214", + "name": "Suns" + } + }, + "team215": { + "id": "team215", + "labels": [ + "team" + ], + "props": { + "id": "team215", + "name": "Hornets" + } + }, + "team218": { + "id": "team218", + "labels": [ + "team" + ], + "props": { + "id": "team218", + "name": "Raptors" + } + }, + "team219": { + "id": "team219", + "labels": [ + "team" + ], + "props": { + "id": "team219", + "name": "76ers" + } + }, + "team221": { + "id": "team221", + "labels": [ + "team" + ], + "props": { + "id": "team221", + "name": "Bulls" + } + }, + "team222": { + "id": "team222", + "labels": [ + "team" + ], + "props": { + "id": "team222", + "name": "Hawks" + } + } + } +} \ No newline at end of file diff --git a/nebula3/data/ResultSet.py b/nebula3/data/ResultSet.py index 7bafccdc..15d23518 100644 --- a/nebula3/data/ResultSet.py +++ b/nebula3/data/ResultSet.py @@ -248,7 +248,7 @@ def dict_for_vis(self): } }, 'edges_dict': { - ('player100', 'player101', 0, 'follow'): { + "('player100', 'player101', 0, 'follow')": { 'src': 'player100', 'dst': 'player101', 'name': 'follow', @@ -303,8 +303,8 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): k: str(v.cast()) if hasattr(v, "cast") else str(v) for k, v in props_raw.items() } - if (src_id, dst_id, rank, edge_name) not in edges_dict: - edges_dict[(src_id, dst_id, rank, edge_name)] = { + if str((src_id, dst_id, rank, edge_name)) not in edges_dict: + edges_dict[str((src_id, dst_id, rank, edge_name))] = { "src": src_id, "dst": dst_id, "name": edge_name, From e15f217a2d1b6f99c06adcf006de1f3414f0008d Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Sat, 16 Mar 2024 05:50:50 +0000 Subject: [PATCH 15/16] fix: not run GraphVis example in CI --- example/GraphVis.py | 50 ++++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/example/GraphVis.py b/example/GraphVis.py index 41e87dd1..14beb5a9 100644 --- a/example/GraphVis.py +++ b/example/GraphVis.py @@ -7,47 +7,37 @@ import json -import time from nebula3.gclient.net import ConnectionPool -if __name__ == "__main__": - client = None - try: - # init connection pool - connection_pool = ConnectionPool() - assert connection_pool.init([("127.0.0.1", 9669)]) - # get session from the pool - client = connection_pool.get_session("root", "nebula") - assert client is not None +def get_node_list_and_edge_list_json(result): + # init connection pool + connection_pool = ConnectionPool() + assert connection_pool.init([("127.0.0.1", 9669)]) - client.execute("USE nba") + # get session from the pool + client = connection_pool.get_session("root", "nebula") + assert client is not None - result = client.execute( - 'GET SUBGRAPH WITH PROP 2 STEPS FROM "player101" YIELD VERTICES AS nodes, EDGES AS relationships;' - ) + client.execute("USE nba") - assert result.is_succeeded(), result.error_msg() + result = client.execute( + 'GET SUBGRAPH WITH PROP 2 STEPS FROM "player101" YIELD VERTICES AS nodes, EDGES AS relationships;' + ) - data = result.dict_for_vis() + assert result.is_succeeded(), result.error_msg() - json_data = json.dumps(data, indent=2, sort_keys=True) + data = result.dict_for_vis() - # save the json data to a file - with open('data.json', 'w') as f: - f.write(json_data) + json_data = json.dumps(data, indent=2, sort_keys=True) - # Check the data.json file to see the result + # save the json data to a file + with open('data.json', 'w') as f: + f.write(json_data) - # See example/apache_echarts.html to see a reference implementation of the visualization - # using Apache ECharts + # Check the data.json file to see the result - except Exception: - import traceback - - print(traceback.format_exc()) - if client is not None: - client.release() - exit(1) + # See example/apache_echarts.html to see a reference implementation of the visualization + # using Apache ECharts From bf072ce80bf6f94e8db238e3631d3c9ad46b1a66 Mon Sep 17 00:00:00 2001 From: Wey Gu Date: Mon, 18 Mar 2024 05:57:19 +0000 Subject: [PATCH 16/16] address Hao Xin's comment --- README.md | 2 +- example/FormatResp.py | 2 +- nebula3/data/ResultSet.py | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f7259c46..e14d096d 100644 --- a/README.md +++ b/README.md @@ -218,7 +218,7 @@ pip3 install pandas ```python result = session.execute('') -df = result.to_pandas() +df = result.as_data_frame() ```
diff --git a/example/FormatResp.py b/example/FormatResp.py index 87232ae2..cfbbd599 100644 --- a/example/FormatResp.py +++ b/example/FormatResp.py @@ -24,7 +24,7 @@ def result_to_df_buildin(result: ResultSet) -> pd.DataFrame: build list for each column, and transform to dataframe """ assert result.is_succeeded() - return result.as_pandas() + return result.as_data_frame() ################################ diff --git a/nebula3/data/ResultSet.py b/nebula3/data/ResultSet.py index 15d23518..441329e5 100644 --- a/nebula3/data/ResultSet.py +++ b/nebula3/data/ResultSet.py @@ -350,12 +350,13 @@ def add_to_nodes_or_edges(nodes_dict, edges_dict, item): "edges_count": len(edges), } - def as_pandas(self, primitive: bool = True): - """Convert result set to a pandas DataFrame. + def as_data_frame(self, primitive: bool = True): + """Convert result set to a DataFrame. :param primitive: if True, convert all values to primitive types - :return: pandas DataFrame + :return: DataFrame """ + # TODO: support polars df try: import pandas as pd except ImportError: