pyneoinstance 2.0.2__tar.gz → 2.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyneoinstance-2.0.2/pyneoinstance.egg-info → pyneoinstance-2.2.0}/PKG-INFO +3 -2
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/__init__.py +2 -1
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/database/context.py +1 -1
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/database/neo4jdbms.py +21 -6
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/util/__init__.py +2 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/util/functions.py +43 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0/pyneoinstance.egg-info}/PKG-INFO +3 -2
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyproject.toml +1 -1
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test_neo4jdb.py +3 -2
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test_util_functions.py +25 -1
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/LICENSE +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/MANIFEST.in +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/README.md +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/database/__init__.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/__init__.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/context.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/helpers.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/loadyaml.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/SOURCES.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/dependency_links.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/requires.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/top_level.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/setup.cfg +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/setup.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/__init__.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/context.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/cypher_queries.yaml +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/load_files.yaml +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/movies.csv +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/people.csv +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/roles.csv +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test_fileload.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyneoinstance
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Handle Neo4j read and write transactions and provide methos for performing graph EDA
|
|
5
5
|
Author-email: "Jose A. Alvarado-Guzman" <jose.alvarado-guzman@neo4j.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -689,6 +689,7 @@ Requires-Dist: neo4j>=5.0.0
|
|
|
689
689
|
Requires-Dist: pandas
|
|
690
690
|
Requires-Dist: PyYAML
|
|
691
691
|
Requires-Dist: pyvis
|
|
692
|
+
Dynamic: license-file
|
|
692
693
|
|
|
693
694
|
# PyNeoInstance Package
|
|
694
695
|
|
|
@@ -22,6 +22,7 @@ from neo4j.exceptions import ServiceUnavailable
|
|
|
22
22
|
from neo4j.exceptions import AuthError
|
|
23
23
|
from neo4j.exceptions import ClientError
|
|
24
24
|
from neo4j.exceptions import ConfigurationError
|
|
25
|
+
from .context import get_logger, get_parallel_info, get_columns_diff
|
|
25
26
|
|
|
26
27
|
def _get_driver(neo_info : Dict[str, str]):
|
|
27
28
|
try:
|
|
@@ -169,7 +170,12 @@ class Neo4jInstance:
|
|
|
169
170
|
self.neo_info['password'] = password
|
|
170
171
|
self.neo_info['encrypted'] = kwargs.get('encrypted') or ''
|
|
171
172
|
self.__results = defaultdict(int)
|
|
172
|
-
|
|
173
|
+
self.__logger = get_logger('pyneoinstance', logging.WARNING)
|
|
174
|
+
stream_handler = logging.StreamHandler()
|
|
175
|
+
formatter = logging.Formatter(
|
|
176
|
+
'%(levelname)s - %(message)s')
|
|
177
|
+
stream_handler.setFormatter(formatter)
|
|
178
|
+
self.__logger.addHandler(stream_handler)
|
|
173
179
|
def execute_read_query(self, query: str,
|
|
174
180
|
database: Optional[str] = None,
|
|
175
181
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -289,7 +295,8 @@ class Neo4jInstance:
|
|
|
289
295
|
result = self.execute_write_queries([query], database, parameters)
|
|
290
296
|
return result
|
|
291
297
|
|
|
292
|
-
def execute_write_queries_with_data(self, queries: List[str],
|
|
298
|
+
def execute_write_queries_with_data(self, queries: List[str],
|
|
299
|
+
data: DataFrame,
|
|
293
300
|
database: Optional[str] = None,
|
|
294
301
|
partitions: Optional[int] = 1,
|
|
295
302
|
parallel: Optional[bool] = False,
|
|
@@ -335,7 +342,7 @@ class Neo4jInstance:
|
|
|
335
342
|
if partitions > data.shape[0]:
|
|
336
343
|
raise ValueError(
|
|
337
344
|
"The batch size cannot be greater than the number of rows in the data.")
|
|
338
|
-
data_chunks = np.array_split(data,partitions)
|
|
345
|
+
data_chunks = np.array_split(data.index,partitions)
|
|
339
346
|
if parameters:
|
|
340
347
|
params = parameters
|
|
341
348
|
else:
|
|
@@ -346,19 +353,27 @@ class Neo4jInstance:
|
|
|
346
353
|
else:
|
|
347
354
|
workers_num = mp.cpu_count()
|
|
348
355
|
for query in queries:
|
|
356
|
+
col_diff = get_columns_diff(query,data.columns)
|
|
357
|
+
msg = f'This columns are not in your data: {col_diff}'
|
|
358
|
+
if len(col_diff)!=0:
|
|
359
|
+
self.__logger.warning(msg)
|
|
349
360
|
with mp.Pool(workers_num) as worker_pool:
|
|
350
361
|
results = worker_pool.starmap(
|
|
351
362
|
_execute_write_parallel,
|
|
352
|
-
[(self.neo_info, database,query, d.
|
|
363
|
+
[(self.neo_info, database,query, data.iloc[d].to_dict('records'),
|
|
353
364
|
params) for d in data_chunks]
|
|
354
365
|
)
|
|
355
366
|
else:
|
|
356
367
|
results = []
|
|
357
368
|
with _get_driver(self.neo_info) as driver:
|
|
358
369
|
with _get_session(driver, database) as session:
|
|
359
|
-
for
|
|
360
|
-
rows_dict = {'rows':
|
|
370
|
+
for idx in data_chunks:
|
|
371
|
+
rows_dict = {'rows': data.iloc[idx].to_dict('records')}
|
|
361
372
|
for query in queries:
|
|
373
|
+
col_diff = get_columns_diff(query,data.columns)
|
|
374
|
+
msg = f'This columns are not in your data: {col_diff}'
|
|
375
|
+
if len(col_diff)!=0:
|
|
376
|
+
self.__logger.warning(msg)
|
|
362
377
|
results.append(_execute_write(session,
|
|
363
378
|
query,
|
|
364
379
|
rows_dict['rows'],
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import functools
|
|
5
5
|
import time
|
|
6
6
|
import logging
|
|
7
|
+
import re
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from typing import Tuple, Any, Callable, List
|
|
9
10
|
from urllib.request import urlopen
|
|
@@ -94,3 +95,45 @@ def timing(function : Callable) -> Tuple[Any, float]:
|
|
|
94
95
|
elapsed_time = end - start * 1.0
|
|
95
96
|
return result, elapsed_time
|
|
96
97
|
return wrap
|
|
98
|
+
|
|
99
|
+
def get_parallel_info(num_rows: int, partition_rows=100_000) -> Tuple[int,bool]:
|
|
100
|
+
"""Get the number of partitions and whether to execute execute
|
|
101
|
+
the load in parallel based on the number of rows of a Data Frame.
|
|
102
|
+
|
|
103
|
+
Parameters
|
|
104
|
+
----------
|
|
105
|
+
num_rows : int
|
|
106
|
+
Number of rows of the Data Frame
|
|
107
|
+
partition_rows : int
|
|
108
|
+
Number of records per partition
|
|
109
|
+
Returns
|
|
110
|
+
-------
|
|
111
|
+
Tuble[int, bool]
|
|
112
|
+
Tuple containing the number of partitions and parallelims.
|
|
113
|
+
"""
|
|
114
|
+
partitions = 1
|
|
115
|
+
parallel = False
|
|
116
|
+
batches = int(num_rows/partition_rows)
|
|
117
|
+
if batches > 1:
|
|
118
|
+
partitions = batches
|
|
119
|
+
parallel = True
|
|
120
|
+
return (partitions, parallel)
|
|
121
|
+
|
|
122
|
+
def get_columns_diff(query: str, columns: List[str]) -> List[str]:
|
|
123
|
+
"""Get a list of the columns that a Cypher query is trying to access that are
|
|
124
|
+
not in the list of columns provided.
|
|
125
|
+
|
|
126
|
+
Parameters
|
|
127
|
+
----------
|
|
128
|
+
query : str
|
|
129
|
+
Cypher query
|
|
130
|
+
columns : List[str]
|
|
131
|
+
List containing the column names of a Pandas Data Frame
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
List[str]
|
|
135
|
+
List containing the columns that Cypher is trying to access that are not
|
|
136
|
+
included in the columns names of the data frame.
|
|
137
|
+
"""
|
|
138
|
+
cypher_attributes = set([w.split('.')[1] for w in re.findall(r'\brow\.\w+',query)])
|
|
139
|
+
return [a for a in cypher_attributes if a not in columns]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyneoinstance
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Handle Neo4j read and write transactions and provide methos for performing graph EDA
|
|
5
5
|
Author-email: "Jose A. Alvarado-Guzman" <jose.alvarado-guzman@neo4j.com>
|
|
6
6
|
License: GNU GENERAL PUBLIC LICENSE
|
|
@@ -689,6 +689,7 @@ Requires-Dist: neo4j>=5.0.0
|
|
|
689
689
|
Requires-Dist: pandas
|
|
690
690
|
Requires-Dist: PyYAML
|
|
691
691
|
Requires-Dist: pyvis
|
|
692
|
+
Dynamic: license-file
|
|
692
693
|
|
|
693
694
|
# PyNeoInstance Package
|
|
694
695
|
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyneoinstance"
|
|
9
|
-
version = "2.0
|
|
9
|
+
version = "2.2.0"
|
|
10
10
|
description = "Handle Neo4j read and write transactions and provide methos for performing graph EDA"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
authors = [{ name = "Jose A. Alvarado-Guzman",email = "jose.alvarado-guzman@neo4j.com" }]
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import os
|
|
10
10
|
import unittest
|
|
11
11
|
import pandas as pd
|
|
12
|
+
import numpy as np
|
|
12
13
|
import pandas.testing as pd_testing
|
|
13
14
|
from neo4j.exceptions import ClientError
|
|
14
15
|
from .context import database
|
|
@@ -121,7 +122,7 @@ class TestNeo4jInstance(unittest.TestCase):
|
|
|
121
122
|
# Test get_node_labels_freq
|
|
122
123
|
print('Get node labels freq')
|
|
123
124
|
role_file = os.path.join(test_dir,'roles.csv')
|
|
124
|
-
role_df = pd.read_csv(role_file)
|
|
125
|
+
role_df = pd.read_csv(role_file).replace(np.NaN,'')
|
|
125
126
|
result = self.graph.get_node_label_freq(
|
|
126
127
|
self.queries['database'])
|
|
127
128
|
solution = pd.DataFrame([
|
|
@@ -194,7 +195,7 @@ class TestNeo4jInstance(unittest.TestCase):
|
|
|
194
195
|
{'sourceLabel':'Person','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914}
|
|
195
196
|
]
|
|
196
197
|
)
|
|
197
|
-
solution.index = [2,0,1]
|
|
198
|
+
#solution.index = [2,0,1]
|
|
198
199
|
self.assertEqual(result,solution)
|
|
199
200
|
|
|
200
201
|
if __name__ == '__main__':
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
"""Module use to test the utility functions"""
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
+
import pandas as pd
|
|
5
6
|
import unittest
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from .context import util
|
|
9
|
+
from .context import fileload
|
|
8
10
|
|
|
9
|
-
class
|
|
11
|
+
class TestUtilFunctions(unittest.TestCase):
|
|
10
12
|
"""Test the util function to generate file names.
|
|
11
13
|
|
|
12
14
|
Methods
|
|
@@ -36,3 +38,25 @@ class TestGetFileName(unittest.TestCase):
|
|
|
36
38
|
url = url_scheme + '://' + url_loc + '/' + file_name
|
|
37
39
|
result = util.is_reachable_url(url)
|
|
38
40
|
self.assertTrue(result)
|
|
41
|
+
|
|
42
|
+
def test_parallel_info(self):
|
|
43
|
+
print('Parallel Info')
|
|
44
|
+
"""Test the get_parallel_function."""
|
|
45
|
+
result = util.get_parallel_info(1000)
|
|
46
|
+
solution = (1,False)
|
|
47
|
+
self.assertEqual(solution,result)
|
|
48
|
+
result = util.get_parallel_info(1000000)
|
|
49
|
+
solution = (10,True)
|
|
50
|
+
self.assertEqual(solution,result)
|
|
51
|
+
|
|
52
|
+
def test_get_colums_diff(self):
|
|
53
|
+
print('Columns Diff')
|
|
54
|
+
data_file_name = 'movies.csv'
|
|
55
|
+
cypher_file_name = 'cypher_queries.yaml'
|
|
56
|
+
file_location = os.path.abspath(os.path.dirname(__file__))
|
|
57
|
+
cypher_file = os.path.join(file_location,cypher_file_name)
|
|
58
|
+
query = fileload.load_yaml_file(cypher_file)['queries']['load_movie']
|
|
59
|
+
columns = pd.read_csv(os.path.join(file_location,data_file_name)).columns
|
|
60
|
+
result = util.get_columns_diff(query, columns)
|
|
61
|
+
solution = []
|
|
62
|
+
self.assertEqual(solution,result)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|