From b94aca944c04a48671aeeccbf523e1f3f0eea84b Mon Sep 17 00:00:00 2001 From: Chang Liu Date: Mon, 19 Dec 2022 11:16:15 -0500 Subject: [PATCH] Wrap pybind11 API and and Fix #1106 --- tools/python_api/CMakeLists.txt | 22 +++++---- tools/python_api/__init__.py | 0 .../{ => src_cpp}/include/py_connection.h | 0 .../{ => src_cpp}/include/py_database.h | 0 .../{ => src_cpp}/include/py_query_result.h | 0 .../include/py_query_result_converter.h | 0 .../{ => src_cpp}/include/pybind_include.h | 0 .../python_api/{ => src_cpp}/kuzu_binding.cpp | 0 .../{ => src_cpp}/py_connection.cpp | 2 +- .../python_api/{ => src_cpp}/py_database.cpp | 2 +- .../{ => src_cpp}/py_query_result.cpp | 0 .../py_query_result_converter.cpp | 0 tools/python_api/src_py/__init__.py | 5 ++ tools/python_api/src_py/connection.py | 14 ++++++ tools/python_api/src_py/query_result.py | 46 ++++++++++++++++++ tools/python_api/test/conftest.py | 17 ++++--- tools/python_api/test/example.py | 12 +++-- tools/python_api/test/test_datatype.py | 48 +++++++++---------- tools/python_api/test/test_df.py | 4 +- tools/python_api/test/test_get_header.py | 4 +- tools/python_api/test/test_main.py | 1 + tools/python_api/test/test_parameter.py | 36 +++++++------- .../test/test_query_result_close.py | 23 +++++++++ tools/python_api/test/test_write_to_csv.py | 48 +++++++++++++------ 24 files changed, 202 insertions(+), 82 deletions(-) delete mode 100644 tools/python_api/__init__.py rename tools/python_api/{ => src_cpp}/include/py_connection.h (100%) rename tools/python_api/{ => src_cpp}/include/py_database.h (100%) rename tools/python_api/{ => src_cpp}/include/py_query_result.h (100%) rename tools/python_api/{ => src_cpp}/include/py_query_result_converter.h (100%) rename tools/python_api/{ => src_cpp}/include/pybind_include.h (100%) rename tools/python_api/{ => src_cpp}/kuzu_binding.cpp (100%) rename tools/python_api/{ => src_cpp}/py_connection.cpp (98%) rename tools/python_api/{ => src_cpp}/py_database.cpp (95%) rename tools/python_api/{ => src_cpp}/py_query_result.cpp (100%) rename tools/python_api/{ => src_cpp}/py_query_result_converter.cpp (100%) create mode 100644 tools/python_api/src_py/__init__.py create mode 100644 tools/python_api/src_py/connection.py create mode 100644 tools/python_api/src_py/query_result.py create mode 100644 tools/python_api/test/test_query_result_close.py diff --git a/tools/python_api/CMakeLists.txt b/tools/python_api/CMakeLists.txt index 572a40709b..922abbbd6b 100644 --- a/tools/python_api/CMakeLists.txt +++ b/tools/python_api/CMakeLists.txt @@ -3,17 +3,20 @@ project(_kuzu) set(CMAKE_CXX_STANDARD 20) +file(GLOB SOURCE_PY + "src_py/*") + pybind11_add_module(_kuzu SHARED - kuzu_binding.cpp - py_connection.cpp - py_database.cpp - py_query_result.cpp - py_query_result_converter.cpp) + src_cpp/kuzu_binding.cpp + src_cpp/py_connection.cpp + src_cpp/py_database.cpp + src_cpp/py_query_result.cpp + src_cpp/py_query_result_converter.cpp) set_target_properties(_kuzu PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/") + LIBRARY_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/build/kuzu") target_link_libraries(_kuzu PRIVATE @@ -22,5 +25,8 @@ target_link_libraries(_kuzu target_include_directories( _kuzu PUBLIC - ../../src/include -) + ../../src/include) + +get_target_property(PYTHON_DEST _kuzu LIBRARY_OUTPUT_DIRECTORY) + +file(COPY ${SOURCE_PY} DESTINATION ${PYTHON_DEST}) diff --git a/tools/python_api/__init__.py b/tools/python_api/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/python_api/include/py_connection.h b/tools/python_api/src_cpp/include/py_connection.h similarity index 100% rename from tools/python_api/include/py_connection.h rename to tools/python_api/src_cpp/include/py_connection.h diff --git a/tools/python_api/include/py_database.h b/tools/python_api/src_cpp/include/py_database.h similarity index 100% rename from tools/python_api/include/py_database.h rename to tools/python_api/src_cpp/include/py_database.h diff --git a/tools/python_api/include/py_query_result.h b/tools/python_api/src_cpp/include/py_query_result.h similarity index 100% rename from tools/python_api/include/py_query_result.h rename to tools/python_api/src_cpp/include/py_query_result.h diff --git a/tools/python_api/include/py_query_result_converter.h b/tools/python_api/src_cpp/include/py_query_result_converter.h similarity index 100% rename from tools/python_api/include/py_query_result_converter.h rename to tools/python_api/src_cpp/include/py_query_result_converter.h diff --git a/tools/python_api/include/pybind_include.h b/tools/python_api/src_cpp/include/pybind_include.h similarity index 100% rename from tools/python_api/include/pybind_include.h rename to tools/python_api/src_cpp/include/pybind_include.h diff --git a/tools/python_api/kuzu_binding.cpp b/tools/python_api/src_cpp/kuzu_binding.cpp similarity index 100% rename from tools/python_api/kuzu_binding.cpp rename to tools/python_api/src_cpp/kuzu_binding.cpp diff --git a/tools/python_api/py_connection.cpp b/tools/python_api/src_cpp/py_connection.cpp similarity index 98% rename from tools/python_api/py_connection.cpp rename to tools/python_api/src_cpp/py_connection.cpp index 446b78f711..7d3ba0afcd 100644 --- a/tools/python_api/py_connection.cpp +++ b/tools/python_api/src_cpp/py_connection.cpp @@ -3,7 +3,7 @@ #include "datetime.h" // from Python void PyConnection::initialize(py::handle& m) { - py::class_(m, "connection") + py::class_(m, "Connection") .def(py::init(), py::arg("database"), py::arg("num_threads") = 0) .def( "execute", &PyConnection::execute, py::arg("query"), py::arg("parameters") = py::list()) diff --git a/tools/python_api/py_database.cpp b/tools/python_api/src_cpp/py_database.cpp similarity index 95% rename from tools/python_api/py_database.cpp rename to tools/python_api/src_cpp/py_database.cpp index b854b1a936..d52c06a40a 100644 --- a/tools/python_api/py_database.cpp +++ b/tools/python_api/src_cpp/py_database.cpp @@ -1,7 +1,7 @@ #include "include/py_database.h" void PyDatabase::initialize(py::handle& m) { - py::class_(m, "database") + py::class_(m, "Database") .def(py::init(), py::arg("database_path"), py::arg("buffer_pool_size") = 0) .def("resize_buffer_manager", &PyDatabase::resizeBufferManager, py::arg("new_size")) diff --git a/tools/python_api/py_query_result.cpp b/tools/python_api/src_cpp/py_query_result.cpp similarity index 100% rename from tools/python_api/py_query_result.cpp rename to tools/python_api/src_cpp/py_query_result.cpp diff --git a/tools/python_api/py_query_result_converter.cpp b/tools/python_api/src_cpp/py_query_result_converter.cpp similarity index 100% rename from tools/python_api/py_query_result_converter.cpp rename to tools/python_api/src_cpp/py_query_result_converter.cpp diff --git a/tools/python_api/src_py/__init__.py b/tools/python_api/src_py/__init__.py new file mode 100644 index 0000000000..483f82c2c2 --- /dev/null +++ b/tools/python_api/src_py/__init__.py @@ -0,0 +1,5 @@ +from ._kuzu import * +# The following imports will override C++ implementations with Python +# implementations. +from .connection import * +from .query_result import * diff --git a/tools/python_api/src_py/connection.py b/tools/python_api/src_py/connection.py new file mode 100644 index 0000000000..694ca151e6 --- /dev/null +++ b/tools/python_api/src_py/connection.py @@ -0,0 +1,14 @@ +from .query_result import QueryResult +from . import _kuzu + + +class Connection: + def __init__(self, database, num_threads=0): + self.database = database + self._connection = _kuzu.Connection(database, num_threads) + + def set_max_threads_for_exec(self, num_threads): + self._connection.set_max_threads_for_exec(num_threads) + + def execute(self, query, parameters=[]): + return QueryResult(self, self._connection.execute(query, parameters)) diff --git a/tools/python_api/src_py/query_result.py b/tools/python_api/src_py/query_result.py new file mode 100644 index 0000000000..aae358bb11 --- /dev/null +++ b/tools/python_api/src_py/query_result.py @@ -0,0 +1,46 @@ +class QueryResult: + def __init__(self, connection, query_result): + self.connection = connection + self._query_result = query_result + self.is_closed = False + + def __del__(self): + self.close() + + def check_for_query_result_close(self): + if self.is_closed: + raise Exception("Query result is closed") + + def has_next(self): + self.check_for_query_result_close() + return self._query_result.hasNext() + + def get_next(self): + self.check_for_query_result_close() + return self._query_result.getNext() + + def write_to_csv(self, filename, delimiter=',', escapeCharacter='"', newline='\n'): + self.check_for_query_result_close() + self._query_result.writeToCSV( + filename, delimiter, escapeCharacter, newline) + + def close(self): + if self.is_closed: + return + self._query_result.close() + # Allows the connection to be garbage collected if the query result + # is closed manually by the user. + self.connection = None + self.is_closed = True + + def get_as_df(self): + self.check_for_query_result_close() + return self._query_result.getAsDF() + + def get_column_data_types(self): + self.check_for_query_result_close() + return self._query_result.getColumnDataTypes() + + def get_column_names(self): + self.check_for_query_result_close() + return self._query_result.getColumnNames() diff --git a/tools/python_api/test/conftest.py b/tools/python_api/test/conftest.py index 36a9cdde7b..c59b6a082f 100644 --- a/tools/python_api/test/conftest.py +++ b/tools/python_api/test/conftest.py @@ -1,18 +1,19 @@ import os import sys import pytest +import shutil sys.path.append('../build/') -import _kuzu as kuzu +import kuzu # Note conftest is the default file name for sharing fixture through multiple test files. Do not change file name. @pytest.fixture def init_tiny_snb(tmp_path): if os.path.exists(tmp_path): - os.rmdir(tmp_path) + shutil.rmtree(tmp_path) output_path = str(tmp_path) - db = kuzu.database(output_path) - conn = kuzu.connection(db) + db = kuzu.Database(output_path) + conn = kuzu.Connection(db) conn.execute("CREATE NODE TABLE person (ID INT64, fName STRING, gender INT64, isStudent BOOLEAN, isWorker BOOLEAN, " "age INT64, eyeSight DOUBLE, birthdate DATE, registerTime TIMESTAMP, lastJobDuration " "INTERVAL, workedHours INT64[], usedNames STRING[], courseScoresPerTerm INT64[][], PRIMARY " @@ -26,6 +27,10 @@ def init_tiny_snb(tmp_path): @pytest.fixture def establish_connection(init_tiny_snb): - db = kuzu.database(init_tiny_snb, buffer_pool_size=256 * 1024 * 1024) - conn = kuzu.connection(db, num_threads=4) + db = kuzu.Database(init_tiny_snb, buffer_pool_size=256 * 1024 * 1024) + conn = kuzu.Connection(db, num_threads=4) return conn, db + +@pytest.fixture +def get_tmp_path(tmp_path): + return str(tmp_path) diff --git a/tools/python_api/test/example.py b/tools/python_api/test/example.py index 9bf73f4d01..9ec4fe4feb 100644 --- a/tools/python_api/test/example.py +++ b/tools/python_api/test/example.py @@ -1,10 +1,12 @@ -from tools.python_api import _kuzu as kuzu +import sys +sys.path.append('../build/') +import kuzu databaseDir = "path to database file" -db = kuzu.database(databaseDir) -conn = kuzu.connection(db) +db = kuzu.Database(databaseDir) +conn = kuzu.Connection(db) query = "MATCH (a:person) RETURN *;" result = conn.execute(query) -while result.hasNext(): - print(result.getNext()) +while result.has_next(): + print(result.get_next()) result.close() diff --git a/tools/python_api/test/test_datatype.py b/tools/python_api/test/test_datatype.py index 1ebd4f32a8..90ac8cea1d 100644 --- a/tools/python_api/test/test_datatype.py +++ b/tools/python_api/test/test_datatype.py @@ -4,71 +4,71 @@ def test_bool_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.isStudent;") - assert result.hasNext() - assert result.getNext() == [True] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [True] + assert not result.has_next() result.close() def test_int_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.age;") - assert result.hasNext() - assert result.getNext() == [35] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [35] + assert not result.has_next() result.close() def test_double_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.eyeSight;") - assert result.hasNext() - assert result.getNext() == [5.0] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [5.0] + assert not result.has_next() result.close() def test_string_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.fName;") - assert result.hasNext() - assert result.getNext() == ['Alice'] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == ['Alice'] + assert not result.has_next() result.close() def test_date_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.birthdate;") - assert result.hasNext() - assert result.getNext() == [datetime.date(1900, 1, 1)] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [datetime.date(1900, 1, 1)] + assert not result.has_next() result.close() def test_timestamp_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.registerTime;") - assert result.hasNext() - assert result.getNext() == [datetime.datetime(2011, 8, 20, 11, 25, 30)] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [datetime.datetime(2011, 8, 20, 11, 25, 30)] + assert not result.has_next() result.close() def test_interval_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.lastJobDuration;") - assert result.hasNext() - assert result.getNext() == [datetime.timedelta(days=1082, seconds=46920)] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [datetime.timedelta(days=1082, seconds=46920)] + assert not result.has_next() result.close() def test_list_wrap(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.courseScoresPerTerm;") - assert result.hasNext() - assert result.getNext() == [[[10, 8], [6, 7, 8]]] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [[[10, 8], [6, 7, 8]]] + assert not result.has_next() result.close() diff --git a/tools/python_api/test/test_df.py b/tools/python_api/test/test_df.py index 4890869e7e..87177ececa 100644 --- a/tools/python_api/test/test_df.py +++ b/tools/python_api/test/test_df.py @@ -1,7 +1,7 @@ import numpy as np import sys sys.path.append('../build/') -import _kuzu as kuzu +import kuzu from pandas import Timestamp, Timedelta, isna def test_to_df(establish_connection): @@ -9,7 +9,7 @@ def test_to_df(establish_connection): def _test_to_df(conn): query = "MATCH (p:person) return * ORDER BY p.ID" - pd = conn.execute(query).getAsDF() + pd = conn.execute(query).get_as_df() assert pd['p.ID'].tolist() == [0, 2, 3, 5, 7, 8, 9, 10] assert str(pd['p.ID'].dtype) == "int64" assert pd['p.fName'].tolist() == ["Alice", "Bob", "Carol", "Dan", "Elizabeth", "Farooq", "Greg", diff --git a/tools/python_api/test/test_get_header.py b/tools/python_api/test/test_get_header.py index d48295fb7d..aa17b67fec 100644 --- a/tools/python_api/test/test_get_header.py +++ b/tools/python_api/test/test_get_header.py @@ -1,7 +1,7 @@ def test_get_column_names(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person)-[e:knows]->(b:person) RETURN a.fName, e.date, b.ID;") - column_names = result.getColumnNames() + column_names = result.get_column_names() assert column_names[0] == 'a.fName' assert column_names[1] == 'e.date' assert column_names[2] == 'b.ID' @@ -13,7 +13,7 @@ def test_get_column_data_types(establish_connection): result = conn.execute( "MATCH (p:person) RETURN p.ID, p.fName, p.isStudent, p.eyeSight, p.birthdate, p.registerTime, " "p.lastJobDuration, p.workedHours, p.courseScoresPerTerm;") - column_data_types = result.getColumnDataTypes() + column_data_types = result.get_column_data_types() assert column_data_types[0] == 'INT64' assert column_data_types[1] == 'STRING' assert column_data_types[2] == 'BOOL' diff --git a/tools/python_api/test/test_main.py b/tools/python_api/test/test_main.py index 6b99378b46..d230d70129 100644 --- a/tools/python_api/test/test_main.py +++ b/tools/python_api/test/test_main.py @@ -6,6 +6,7 @@ from test_df import * from test_write_to_csv import * from test_get_header import * +from test_query_result_close import * if __name__ == "__main__": raise SystemExit(pytest.main([__file__])) diff --git a/tools/python_api/test/test_parameter.py b/tools/python_api/test/test_parameter.py index 57d7608ebd..1b47bfbf05 100644 --- a/tools/python_api/test/test_parameter.py +++ b/tools/python_api/test/test_parameter.py @@ -6,36 +6,36 @@ def test_bool_param(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.isStudent = $1 AND a.isWorker = $k RETURN COUNT(*)", [("1", False), ("k", False)]) - assert result.hasNext() - assert result.getNext() == [1] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [1] + assert not result.has_next() result.close() def test_int_param(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.age < $AGE RETURN COUNT(*)", [("AGE", 1)]) - assert result.hasNext() - assert result.getNext() == [0] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [0] + assert not result.has_next() result.close() def test_double_param(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.eyeSight = $E RETURN COUNT(*)", [("E", 5.0)]) - assert result.hasNext() - assert result.getNext() == [2] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [2] + assert not result.has_next() result.close() def test_str_param(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN concat(a.fName, $S);", [("S", "HH")]) - assert result.hasNext() - assert result.getNext() == ["AliceHH"] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == ["AliceHH"] + assert not result.has_next() result.close() @@ -43,9 +43,9 @@ def test_date_param(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.birthdate = $1 RETURN COUNT(*);", [("1", datetime.date(1900, 1, 1))]) - assert result.hasNext() - assert result.getNext() == [2] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [2] + assert not result.has_next() result.close() @@ -53,9 +53,9 @@ def test_timestamp_param(establish_connection): conn, db = establish_connection result = conn.execute("MATCH (a:person) WHERE a.registerTime = $1 RETURN COUNT(*);", [("1", datetime.datetime(2011, 8, 20, 11, 25, 30))]) - assert result.hasNext() - assert result.getNext() == [1] - assert not result.hasNext() + assert result.has_next() + assert result.get_next() == [1] + assert not result.has_next() result.close() diff --git a/tools/python_api/test/test_query_result_close.py b/tools/python_api/test/test_query_result_close.py new file mode 100644 index 0000000000..21bbbf76a1 --- /dev/null +++ b/tools/python_api/test/test_query_result_close.py @@ -0,0 +1,23 @@ +import subprocess +import sys + + +def test_query_result_close(get_tmp_path): + code = [ + 'import sys', + 'sys.path.append("../build/")', + 'import kuzu', + 'db = kuzu.Database("' + get_tmp_path + '")', + 'conn = kuzu.Connection(db)', + 'conn.execute(\'CREATE NODE TABLE person (ID INT64, fName STRING, gender INT64,\ + isStudent BOOLEAN, isWorker BOOLEAN, age INT64, eyeSight DOUBLE,\ + birthdate DATE, registerTime TIMESTAMP, lastJobDuration INTERVAL,\ + workedHours INT64[], usedNames STRING[], courseScoresPerTerm INT64[][],\ + PRIMARY KEY (ID))\')', + 'conn.execute(\'COPY person FROM \"../../../dataset/tinysnb/vPerson.csv\" (HEADER=true)\')', + 'result = conn.execute("MATCH (a:person) WHERE a.ID = 0 RETURN a.isStudent;")', + # 'result.close()', + ] + code = ';'.join(code) + result = subprocess.run([sys.executable, '-c', code]) + assert result.returncode == 0 diff --git a/tools/python_api/test/test_write_to_csv.py b/tools/python_api/test/test_write_to_csv.py index dd64a1d6eb..e8e742f670 100644 --- a/tools/python_api/test/test_write_to_csv.py +++ b/tools/python_api/test/test_write_to_csv.py @@ -1,29 +1,47 @@ from pandas import read_csv +import os + +TEST_CVS_NAME = "test_PYTHON_CSV.csv" + def test_write_to_csv(establish_connection): outputString = """Alice,[""Aida""],"[10,5]"\nBob,[""Bobby""],"[12,8]"\nCarol,"[""Carmen"",""Fred""]","[4,5]"\nDan,"[""Wolfeschlegelstein"",""Daniel""]","[1,9]"\nElizabeth,[""Ein""],[2]\nFarooq,[""Fesdwe""],"[3,4,5,6,7]"\nGreg,[""Grad""],[1]\nHubert Blaine Wolfeschlegelsteinhausenbergerdorff,"[""Ad"",""De"",""Hi"",""Kye"",""Orlan""]","[10,11,12,3,4,5,6,7]" -""" +""" conn, db = establish_connection - result = conn.execute("MATCH (a:person) RETURN a.fName, a.usedNames, a.workedHours") - result.writeToCSV("test_PYTHON_CSV.csv") - with open("test_PYTHON_CSV.csv") as csv_file: - data = csv_file.read() - assert(data == outputString) + result = conn.execute( + "MATCH (a:person) RETURN a.fName, a.usedNames, a.workedHours") + result.write_to_csv(TEST_CVS_NAME) + result.close() + with open(TEST_CVS_NAME) as csv_file: + data = csv_file.read() + assert (data == outputString) + os.remove(TEST_CVS_NAME) + def test_write_to_csv_extra_args(establish_connection): outputString = """35|1~30|2~45|1~20|2~20|1~25|2~40|2~83|2~""" conn, db = establish_connection result = conn.execute("MATCH (a:person) RETURN a.age, a.gender") - result.writeToCSV("test_PYTHON_CSV.csv", "|", '"', '~') - with open("test_PYTHON_CSV.csv") as csv_file: + result.write_to_csv(TEST_CVS_NAME, "|", '"', '~') + result.close() + with open(TEST_CVS_NAME) as csv_file: data = csv_file.read() - assert(data == outputString) + assert (data == outputString) + os.remove(TEST_CVS_NAME) + def test_pandas_read_csv_extra_args(establish_connection): conn, db = establish_connection - result = conn.execute("MATCH (a:person) RETURN a.fName, a.workedHours, a.usedNames"); - result.writeToCSV("test_PYTHON_CSV.csv", "|", "'", "~"); - df = read_csv("test_PYTHON_CSV.csv", delimiter = "|", lineterminator = "~", escapechar = "`") - assert(df.iloc[:,0].tolist() == ['Bob', 'Carol', 'Dan', 'Elizabeth', 'Farooq', 'Greg', 'Hubert Blaine Wolfeschlegelsteinhausenbergerdorff']) - assert(df.iloc[:,1].tolist() == ['[12,8]', '[4,5]', '[1,9]', '[2]', '[3,4,5,6,7]','[1]','[10,11,12,3,4,5,6,7]']) - assert(df.iloc[:,2].tolist() == ["[''Bobby'']", "[''Carmen'',''Fred'']", "[''Wolfeschlegelstein'',''Daniel'']","[''Ein'']","[''Fesdwe'']","[''Grad'']","[''Ad'',''De'',''Hi'',''Kye'',''Orlan'']"]) + result = conn.execute( + "MATCH (a:person) RETURN a.fName, a.workedHours, a.usedNames") + result.write_to_csv(TEST_CVS_NAME, "|", "'", "~") + result.close() + df = read_csv(TEST_CVS_NAME, delimiter="|", + lineterminator="~", escapechar="`") + assert (df.iloc[:, 0].tolist() == ['Bob', 'Carol', 'Dan', 'Elizabeth', + 'Farooq', 'Greg', 'Hubert Blaine Wolfeschlegelsteinhausenbergerdorff']) + assert (df.iloc[:, 1].tolist() == ['[12,8]', '[4,5]', '[1,9]', + '[2]', '[3,4,5,6,7]', '[1]', '[10,11,12,3,4,5,6,7]']) + assert (df.iloc[:, 2].tolist() == ["[''Bobby'']", "[''Carmen'',''Fred'']", "[''Wolfeschlegelstein'',''Daniel'']", + "[''Ein'']", "[''Fesdwe'']", "[''Grad'']", "[''Ad'',''De'',''Hi'',''Kye'',''Orlan'']"]) + os.remove(TEST_CVS_NAME)