pyneoinstance 2.0.2__tar.gz → 3.0.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-3.0.0}/PKG-INFO +8 -7
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/README.md +6 -6
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/__init__.py +1 -1
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/database/context.py +1 -1
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/database/neo4jdbms.py +95 -67
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/util/__init__.py +2 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/util/functions.py +51 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0/pyneoinstance.egg-info}/PKG-INFO +8 -7
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/SOURCES.txt +1 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyproject.toml +1 -1
- pyneoinstance-3.0.0/test/age_data.csv +300001 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/test_neo4jdb.py +4 -3
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/test_util_functions.py +29 -1
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/LICENSE +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/MANIFEST.in +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/database/__init__.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/fileload/__init__.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/fileload/context.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/fileload/helpers.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance/fileload/loadyaml.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/dependency_links.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/requires.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/pyneoinstance.egg-info/top_level.txt +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/setup.cfg +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/setup.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/__init__.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/context.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/cypher_queries.yaml +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/load_files.yaml +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/movies.csv +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/people.csv +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/roles.csv +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.0}/test/test.py +0 -0
- {pyneoinstance-2.0.2 → pyneoinstance-3.0.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:
|
|
3
|
+
Version: 3.0.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
|
|
|
@@ -829,7 +830,7 @@ We can update a Neo4j database with data contained in a Pandas Data Frame using
|
|
|
829
830
|
|
|
830
831
|
- _data_: Pandas Data Frame containing data to process.
|
|
831
832
|
- _database_: Optional, database on which to execute the queries.
|
|
832
|
-
-
|
|
833
|
+
- _batchSize_: Optional, number of records per batch use to break the Data Frame in smaller chucks.
|
|
833
834
|
- _parallel_: Whether to execute the query using multiple Python process.
|
|
834
835
|
- _workers_: The number of processes to spawn to execute the queries in parallel.
|
|
835
836
|
- _parameters_: Extra arguments containing optional cypher parameters.
|
|
@@ -909,25 +910,25 @@ node_load_queries = config['loading_queries']['nodes']
|
|
|
909
910
|
person_load_result = graph.execute_write_query_with_data(node_load_queries['Person'], person_data, db_info['database'])
|
|
910
911
|
```
|
|
911
912
|
|
|
912
|
-
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the
|
|
913
|
+
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the _batchSize_ argument the number of records per data batch to break this Data Frame into smaller data batches. These partitions will be loaded in different sequential transactions unless you specified loading them in parallel by setting the _parallel_ argument to true. We will discuss this process in the next section.
|
|
913
914
|
|
|
914
915
|
```python
|
|
915
916
|
person_load_result = graph.execute_write_query_with_data(
|
|
916
917
|
node_load_queries['person'],
|
|
917
918
|
person_data,
|
|
918
919
|
db_info['database'],
|
|
919
|
-
|
|
920
|
+
batchSize = 100000
|
|
920
921
|
)
|
|
921
922
|
```
|
|
922
923
|
## Updating a database with data in a Data Frame: Parallel
|
|
923
924
|
|
|
924
|
-
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the
|
|
925
|
+
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the _batchSize_ argument and set the _parallel_ argument to true. By default, this will use all the available virtual CPUs in the machine. If you need to use less, you can specify how many to use by setting the _workers_ argument.
|
|
925
926
|
|
|
926
927
|
```python
|
|
927
928
|
person_load_result = graph.execute_write_query_with_data(
|
|
928
929
|
node_load_queries['person'],
|
|
929
930
|
person_data,
|
|
930
|
-
|
|
931
|
+
batchSize = 100000,
|
|
931
932
|
parallel = True,
|
|
932
933
|
workers = 5
|
|
933
934
|
)
|
|
@@ -137,7 +137,7 @@ We can update a Neo4j database with data contained in a Pandas Data Frame using
|
|
|
137
137
|
|
|
138
138
|
- _data_: Pandas Data Frame containing data to process.
|
|
139
139
|
- _database_: Optional, database on which to execute the queries.
|
|
140
|
-
-
|
|
140
|
+
- _batchSize_: Optional, number of records per batch use to break the Data Frame in smaller chucks.
|
|
141
141
|
- _parallel_: Whether to execute the query using multiple Python process.
|
|
142
142
|
- _workers_: The number of processes to spawn to execute the queries in parallel.
|
|
143
143
|
- _parameters_: Extra arguments containing optional cypher parameters.
|
|
@@ -217,25 +217,25 @@ node_load_queries = config['loading_queries']['nodes']
|
|
|
217
217
|
person_load_result = graph.execute_write_query_with_data(node_load_queries['Person'], person_data, db_info['database'])
|
|
218
218
|
```
|
|
219
219
|
|
|
220
|
-
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the
|
|
220
|
+
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the _batchSize_ argument the number of records per data batch to break this Data Frame into smaller data batches. These partitions will be loaded in different sequential transactions unless you specified loading them in parallel by setting the _parallel_ argument to true. We will discuss this process in the next section.
|
|
221
221
|
|
|
222
222
|
```python
|
|
223
223
|
person_load_result = graph.execute_write_query_with_data(
|
|
224
224
|
node_load_queries['person'],
|
|
225
225
|
person_data,
|
|
226
226
|
db_info['database'],
|
|
227
|
-
|
|
227
|
+
batchSize = 100000
|
|
228
228
|
)
|
|
229
229
|
```
|
|
230
230
|
## Updating a database with data in a Data Frame: Parallel
|
|
231
231
|
|
|
232
|
-
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the
|
|
232
|
+
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the _batchSize_ argument and set the _parallel_ argument to true. By default, this will use all the available virtual CPUs in the machine. If you need to use less, you can specify how many to use by setting the _workers_ argument.
|
|
233
233
|
|
|
234
234
|
```python
|
|
235
235
|
person_load_result = graph.execute_write_query_with_data(
|
|
236
236
|
node_load_queries['person'],
|
|
237
237
|
person_data,
|
|
238
|
-
|
|
238
|
+
batchSize = 100000,
|
|
239
239
|
parallel = True,
|
|
240
240
|
workers = 5
|
|
241
241
|
)
|
|
@@ -305,4 +305,4 @@ schema.show('schema.html',notebook=False)
|
|
|
305
305
|
|
|
306
306
|
For more detailed examples please refer to the following articles:
|
|
307
307
|
* Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
|
|
308
|
-
* Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
|
|
308
|
+
* Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
|
|
@@ -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_batches, get_columns_diff
|
|
25
26
|
|
|
26
27
|
def _get_driver(neo_info : Dict[str, str]):
|
|
27
28
|
try:
|
|
@@ -59,54 +60,6 @@ def _read_transaction_function(transaction, query, **kwargs):
|
|
|
59
60
|
data = DataFrame(results.values(), columns=results.keys())
|
|
60
61
|
return data
|
|
61
62
|
|
|
62
|
-
def _execute_write(session, query,
|
|
63
|
-
rows: Optional[Dict[str, Any]] = None,
|
|
64
|
-
parameters: Optional[Dict[str, Any]] = None
|
|
65
|
-
):
|
|
66
|
-
if parameters:
|
|
67
|
-
params = parameters
|
|
68
|
-
else:
|
|
69
|
-
params = {}
|
|
70
|
-
try:
|
|
71
|
-
if rows:
|
|
72
|
-
results = session.execute_write(
|
|
73
|
-
_write_transaction_function, query,
|
|
74
|
-
rows = rows, **params).counters.__dict__
|
|
75
|
-
else:
|
|
76
|
-
results = session.execute_write(
|
|
77
|
-
_write_transaction_function, query,
|
|
78
|
-
**params).counters.__dict__
|
|
79
|
-
except ServiceUnavailable as exception:
|
|
80
|
-
raise ServiceUnavailable() from exception
|
|
81
|
-
except ClientError as exception:
|
|
82
|
-
raise ClientError() from exception
|
|
83
|
-
return results
|
|
84
|
-
|
|
85
|
-
def _execute_write_parallel(neo_info, database, query,
|
|
86
|
-
rows: Optional[Dict[str, Any]] = None,
|
|
87
|
-
parameters: Optional[Dict[str, Any]] = None
|
|
88
|
-
):
|
|
89
|
-
if parameters:
|
|
90
|
-
params = parameters
|
|
91
|
-
else:
|
|
92
|
-
params = {}
|
|
93
|
-
with _get_driver(neo_info) as driver:
|
|
94
|
-
with _get_session(driver, database) as session:
|
|
95
|
-
try:
|
|
96
|
-
if rows:
|
|
97
|
-
results = session.execute_write(
|
|
98
|
-
_write_transaction_function, query,
|
|
99
|
-
rows = rows, **params).counters.__dict__
|
|
100
|
-
else:
|
|
101
|
-
results = session.execute_write(
|
|
102
|
-
_write_transaction_function, query,
|
|
103
|
-
**params).counters.__dict__
|
|
104
|
-
except ServiceUnavailable as exception:
|
|
105
|
-
raise ServiceUnavailable() from exception
|
|
106
|
-
except ClientError as exception:
|
|
107
|
-
raise ClientError() from exception
|
|
108
|
-
return results
|
|
109
|
-
|
|
110
63
|
class Neo4jInstance:
|
|
111
64
|
"""Class use to handle Neo4j requests.
|
|
112
65
|
|
|
@@ -169,7 +122,12 @@ class Neo4jInstance:
|
|
|
169
122
|
self.neo_info['password'] = password
|
|
170
123
|
self.neo_info['encrypted'] = kwargs.get('encrypted') or ''
|
|
171
124
|
self.__results = defaultdict(int)
|
|
172
|
-
|
|
125
|
+
self.__logger = get_logger('pyneoinstance', logging.WARNING)
|
|
126
|
+
stream_handler = logging.StreamHandler()
|
|
127
|
+
formatter = logging.Formatter(
|
|
128
|
+
'%(levelname)s - %(message)s')
|
|
129
|
+
stream_handler.setFormatter(formatter)
|
|
130
|
+
self.__logger.addHandler(stream_handler)
|
|
173
131
|
def execute_read_query(self, query: str,
|
|
174
132
|
database: Optional[str] = None,
|
|
175
133
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -251,7 +209,7 @@ class Neo4jInstance:
|
|
|
251
209
|
with _get_driver(self.neo_info) as driver:
|
|
252
210
|
with _get_session(driver, database) as session:
|
|
253
211
|
for query in queries:
|
|
254
|
-
result = _execute_write(session, query, parameters=params)
|
|
212
|
+
result = self._execute_write(session, query, parameters=params)
|
|
255
213
|
for key, value in result.items():
|
|
256
214
|
if key != '_contains_updates':
|
|
257
215
|
results[key] += value
|
|
@@ -289,9 +247,10 @@ class Neo4jInstance:
|
|
|
289
247
|
result = self.execute_write_queries([query], database, parameters)
|
|
290
248
|
return result
|
|
291
249
|
|
|
292
|
-
def execute_write_queries_with_data(self, queries: List[str],
|
|
250
|
+
def execute_write_queries_with_data(self, queries: List[str],
|
|
251
|
+
data: DataFrame,
|
|
293
252
|
database: Optional[str] = None,
|
|
294
|
-
|
|
253
|
+
batchSize: Optional[int] = 100_000,
|
|
295
254
|
parallel: Optional[bool] = False,
|
|
296
255
|
workers: Optional[int] = None,
|
|
297
256
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -307,8 +266,8 @@ class Neo4jInstance:
|
|
|
307
266
|
database : str, optional
|
|
308
267
|
Name of the Neo4j database of which to execute the transactions.
|
|
309
268
|
If not provided the default database is going to be use.
|
|
310
|
-
|
|
311
|
-
The number of
|
|
269
|
+
batchSize : int, optional
|
|
270
|
+
The number of records per batch or partitions of the data frame.
|
|
312
271
|
parallel : bool, optional
|
|
313
272
|
Wheather to execute the load in parallel.
|
|
314
273
|
workers : int, optional
|
|
@@ -332,10 +291,13 @@ class Neo4jInstance:
|
|
|
332
291
|
When the number of batch sizes to split the DataFrame on
|
|
333
292
|
is larger than the number of rows in it.
|
|
334
293
|
"""
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
294
|
+
row_num = data.shape[0]
|
|
295
|
+
batchSize = batchSize if batchSize <= row_num else row_num
|
|
296
|
+
msg = f'Partitioning the data in batches of size {batchSize}'
|
|
297
|
+
self.__logger.info(msg)
|
|
298
|
+
data_chunks = get_batches(data, batchSize)
|
|
299
|
+
partitions = len(data_chunks)
|
|
300
|
+
parallel = parallel if len(data_chunks)>1 else False
|
|
339
301
|
if parameters:
|
|
340
302
|
params = parameters
|
|
341
303
|
else:
|
|
@@ -345,21 +307,33 @@ class Neo4jInstance:
|
|
|
345
307
|
workers_num = workers
|
|
346
308
|
else:
|
|
347
309
|
workers_num = mp.cpu_count()
|
|
310
|
+
msg = f'Loading {partitions:,.0f} data chunks using {workers_num} thread(s)'
|
|
311
|
+
self.__logger.info(msg)
|
|
348
312
|
for query in queries:
|
|
313
|
+
col_diff = get_columns_diff(query,data.columns)
|
|
314
|
+
msg = f'This columns are not in your data: {col_diff}'
|
|
315
|
+
if len(col_diff)!=0:
|
|
316
|
+
self.__logger.warning(msg)
|
|
349
317
|
with mp.Pool(workers_num) as worker_pool:
|
|
350
318
|
results = worker_pool.starmap(
|
|
351
|
-
_execute_write_parallel,
|
|
352
|
-
[(self.neo_info, database,query, d.
|
|
319
|
+
self._execute_write_parallel,
|
|
320
|
+
[(self.neo_info, database,query, data.iloc[d].to_dict('records'),
|
|
353
321
|
params) for d in data_chunks]
|
|
354
322
|
)
|
|
355
323
|
else:
|
|
324
|
+
msg = f'Loading {partitions:,.0f} data chunks sequentially'
|
|
325
|
+
self.__logger.info(msg)
|
|
356
326
|
results = []
|
|
357
327
|
with _get_driver(self.neo_info) as driver:
|
|
358
328
|
with _get_session(driver, database) as session:
|
|
359
|
-
for
|
|
360
|
-
rows_dict = {'rows':
|
|
329
|
+
for idx in data_chunks:
|
|
330
|
+
rows_dict = {'rows': data.iloc[idx].to_dict('records')}
|
|
361
331
|
for query in queries:
|
|
362
|
-
|
|
332
|
+
col_diff = get_columns_diff(query,data.columns)
|
|
333
|
+
msg = f'This columns are not in your data: {col_diff}'
|
|
334
|
+
if len(col_diff)!=0:
|
|
335
|
+
self.__logger.warning(msg)
|
|
336
|
+
results.append(self._execute_write(session,
|
|
363
337
|
query,
|
|
364
338
|
rows_dict['rows'],
|
|
365
339
|
params))
|
|
@@ -369,12 +343,14 @@ class Neo4jInstance:
|
|
|
369
343
|
self.__results[key] += value
|
|
370
344
|
results = dict(self.__results.copy())
|
|
371
345
|
self.__results.clear()
|
|
346
|
+
msg = f'Completed load with results {results}'
|
|
347
|
+
self.__logger.info(msg)
|
|
372
348
|
return results
|
|
373
349
|
|
|
374
350
|
def execute_write_query_with_data(self,
|
|
375
351
|
query: str, data: DataFrame,
|
|
376
352
|
database: Optional[str] = None,
|
|
377
|
-
|
|
353
|
+
batchSize: Optional[int] = 100_000,
|
|
378
354
|
parallel: Optional[bool] = False,
|
|
379
355
|
workers: Optional[int] = None,
|
|
380
356
|
parameters: Optional[Dict[str, Any]] = None
|
|
@@ -390,8 +366,8 @@ class Neo4jInstance:
|
|
|
390
366
|
database : str, optional
|
|
391
367
|
Name of the Neo4j database of which to execute the transaction.
|
|
392
368
|
If not provided the default database is going to be use.
|
|
393
|
-
|
|
394
|
-
The number of
|
|
369
|
+
batchSize : int, optional
|
|
370
|
+
The number of records per batch or partitions of the data frame.
|
|
395
371
|
parallel : bool, optional
|
|
396
372
|
Wheather to execute the load in parallel.
|
|
397
373
|
workers : int, optional
|
|
@@ -420,7 +396,7 @@ class Neo4jInstance:
|
|
|
420
396
|
else:
|
|
421
397
|
params = {}
|
|
422
398
|
result = self.execute_write_queries_with_data(
|
|
423
|
-
[query], data,database,
|
|
399
|
+
[query], data,database, batchSize, parallel, workers, params)
|
|
424
400
|
return result
|
|
425
401
|
|
|
426
402
|
def get_node_label_freq(self, database: Optional[str] = None) -> DataFrame:
|
|
@@ -806,3 +782,55 @@ class Neo4jInstance:
|
|
|
806
782
|
rela_parts = relationship.split("|")
|
|
807
783
|
network.add_edge(rela_parts[0],rela_parts[2],title=rela_parts[1])
|
|
808
784
|
return network
|
|
785
|
+
|
|
786
|
+
def _execute_write_parallel(self, neo_info, database, query,
|
|
787
|
+
rows: Optional[Dict[str, Any]] = None,
|
|
788
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
789
|
+
):
|
|
790
|
+
if parameters:
|
|
791
|
+
params = parameters
|
|
792
|
+
else:
|
|
793
|
+
params = {}
|
|
794
|
+
with _get_driver(neo_info) as driver:
|
|
795
|
+
with _get_session(driver, database) as session:
|
|
796
|
+
try:
|
|
797
|
+
if rows:
|
|
798
|
+
results = session.execute_write(
|
|
799
|
+
_write_transaction_function, query,
|
|
800
|
+
rows = rows, **params).counters.__dict__
|
|
801
|
+
else:
|
|
802
|
+
results = session.execute_write(
|
|
803
|
+
_write_transaction_function, query,
|
|
804
|
+
**params).counters.__dict__
|
|
805
|
+
except ServiceUnavailable as exception:
|
|
806
|
+
raise ServiceUnavailable() from exception
|
|
807
|
+
except ClientError as exception:
|
|
808
|
+
raise ClientError() from exception
|
|
809
|
+
msg = f'Successfully loaded partition with resulting in {results}'
|
|
810
|
+
self.__logger.info(msg)
|
|
811
|
+
return results
|
|
812
|
+
|
|
813
|
+
def _execute_write(self, session, query,
|
|
814
|
+
rows: Optional[Dict[str, Any]] = None,
|
|
815
|
+
parameters: Optional[Dict[str, Any]] = None
|
|
816
|
+
):
|
|
817
|
+
if parameters:
|
|
818
|
+
params = parameters
|
|
819
|
+
else:
|
|
820
|
+
params = {}
|
|
821
|
+
try:
|
|
822
|
+
if rows:
|
|
823
|
+
results = session.execute_write(
|
|
824
|
+
_write_transaction_function, query,
|
|
825
|
+
rows = rows, **params).counters.__dict__
|
|
826
|
+
else:
|
|
827
|
+
results = session.execute_write(
|
|
828
|
+
_write_transaction_function, query,
|
|
829
|
+
**params).counters.__dict__
|
|
830
|
+
except ServiceUnavailable as exception:
|
|
831
|
+
raise ServiceUnavailable() from exception
|
|
832
|
+
except ClientError as exception:
|
|
833
|
+
raise ClientError() from exception
|
|
834
|
+
msg = f'Successfully loaded partition with resulting in {results}'
|
|
835
|
+
self.__logger.info(msg)
|
|
836
|
+
return results
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
import functools
|
|
5
5
|
import time
|
|
6
6
|
import logging
|
|
7
|
+
import re
|
|
8
|
+
import numpy as np
|
|
9
|
+
from pandas import DataFrame
|
|
7
10
|
from datetime import datetime
|
|
8
11
|
from typing import Tuple, Any, Callable, List
|
|
9
12
|
from urllib.request import urlopen
|
|
@@ -94,3 +97,51 @@ def timing(function : Callable) -> Tuple[Any, float]:
|
|
|
94
97
|
elapsed_time = end - start * 1.0
|
|
95
98
|
return result, elapsed_time
|
|
96
99
|
return wrap
|
|
100
|
+
|
|
101
|
+
def get_batches(data: DataFrame, batch_size) -> List[List[int]]:
|
|
102
|
+
"""Partition the data indexes in batches of the specified size.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
data : DataFrame
|
|
107
|
+
Pandas DataFrame containing the data
|
|
108
|
+
batch_size : int
|
|
109
|
+
Number of records per partition
|
|
110
|
+
|
|
111
|
+
Returns
|
|
112
|
+
-------
|
|
113
|
+
List[List[int, bool]]
|
|
114
|
+
List containing the list of indexes of the DataFrame per partition.
|
|
115
|
+
"""
|
|
116
|
+
records = data.shape[0]
|
|
117
|
+
indexes = data.index
|
|
118
|
+
partitions = records // batch_size
|
|
119
|
+
if partitions < 2:
|
|
120
|
+
batches = [list(indexes)]
|
|
121
|
+
else:
|
|
122
|
+
remainder = records % batch_size
|
|
123
|
+
array = np.array(
|
|
124
|
+
indexes[:records-remainder]).reshape(partitions,-1)
|
|
125
|
+
batches = array.tolist()
|
|
126
|
+
if remainder > 0:
|
|
127
|
+
batches.append(indexes[-remainder:])
|
|
128
|
+
return batches
|
|
129
|
+
|
|
130
|
+
def get_columns_diff(query: str, columns: List[str]) -> List[str]:
|
|
131
|
+
"""Get a list of the columns that a Cypher query is trying to access that are
|
|
132
|
+
not in the list of columns provided.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
query : str
|
|
137
|
+
Cypher query
|
|
138
|
+
columns : List[str]
|
|
139
|
+
List containing the column names of a Pandas Data Frame
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
List[str]
|
|
143
|
+
List containing the columns that Cypher is trying to access that are not
|
|
144
|
+
included in the columns names of the data frame.
|
|
145
|
+
"""
|
|
146
|
+
cypher_attributes = set([w.split('.')[1] for w in re.findall(r'\brow\.\w+',query)])
|
|
147
|
+
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:
|
|
3
|
+
Version: 3.0.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
|
|
|
@@ -829,7 +830,7 @@ We can update a Neo4j database with data contained in a Pandas Data Frame using
|
|
|
829
830
|
|
|
830
831
|
- _data_: Pandas Data Frame containing data to process.
|
|
831
832
|
- _database_: Optional, database on which to execute the queries.
|
|
832
|
-
-
|
|
833
|
+
- _batchSize_: Optional, number of records per batch use to break the Data Frame in smaller chucks.
|
|
833
834
|
- _parallel_: Whether to execute the query using multiple Python process.
|
|
834
835
|
- _workers_: The number of processes to spawn to execute the queries in parallel.
|
|
835
836
|
- _parameters_: Extra arguments containing optional cypher parameters.
|
|
@@ -909,25 +910,25 @@ node_load_queries = config['loading_queries']['nodes']
|
|
|
909
910
|
person_load_result = graph.execute_write_query_with_data(node_load_queries['Person'], person_data, db_info['database'])
|
|
910
911
|
```
|
|
911
912
|
|
|
912
|
-
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the
|
|
913
|
+
If the number of rows in your Data Frame is 100K or more it is recommended to break the Data Frame in smaller chunks so that they can be process in different transactions. This will avoid running out of heap error. To do this, just indicate in the _batchSize_ argument the number of records per data batch to break this Data Frame into smaller data batches. These partitions will be loaded in different sequential transactions unless you specified loading them in parallel by setting the _parallel_ argument to true. We will discuss this process in the next section.
|
|
913
914
|
|
|
914
915
|
```python
|
|
915
916
|
person_load_result = graph.execute_write_query_with_data(
|
|
916
917
|
node_load_queries['person'],
|
|
917
918
|
person_data,
|
|
918
919
|
db_info['database'],
|
|
919
|
-
|
|
920
|
+
batchSize = 100000
|
|
920
921
|
)
|
|
921
922
|
```
|
|
922
923
|
## Updating a database with data in a Data Frame: Parallel
|
|
923
924
|
|
|
924
|
-
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the
|
|
925
|
+
To load the data in the Data Frame in parallel, we need to partition the Data Frame by incrementing the _batchSize_ argument and set the _parallel_ argument to true. By default, this will use all the available virtual CPUs in the machine. If you need to use less, you can specify how many to use by setting the _workers_ argument.
|
|
925
926
|
|
|
926
927
|
```python
|
|
927
928
|
person_load_result = graph.execute_write_query_with_data(
|
|
928
929
|
node_load_queries['person'],
|
|
929
930
|
person_data,
|
|
930
|
-
|
|
931
|
+
batchSize = 100000,
|
|
931
932
|
parallel = True,
|
|
932
933
|
workers = 5
|
|
933
934
|
)
|
|
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pyneoinstance"
|
|
9
|
-
version = "
|
|
9
|
+
version = "3.0.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" }]
|