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.
Files changed (33) hide show
  1. {pyneoinstance-2.0.2/pyneoinstance.egg-info → pyneoinstance-2.2.0}/PKG-INFO +3 -2
  2. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/__init__.py +2 -1
  3. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/database/context.py +1 -1
  4. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/database/neo4jdbms.py +21 -6
  5. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/util/__init__.py +2 -0
  6. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/util/functions.py +43 -0
  7. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0/pyneoinstance.egg-info}/PKG-INFO +3 -2
  8. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyproject.toml +1 -1
  9. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test_neo4jdb.py +3 -2
  10. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test_util_functions.py +25 -1
  11. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/LICENSE +0 -0
  12. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/MANIFEST.in +0 -0
  13. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/README.md +0 -0
  14. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/database/__init__.py +0 -0
  15. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/__init__.py +0 -0
  16. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/context.py +0 -0
  17. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/helpers.py +0 -0
  18. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/loadyaml.py +0 -0
  19. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/SOURCES.txt +0 -0
  20. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/dependency_links.txt +0 -0
  21. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/requires.txt +0 -0
  22. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/top_level.txt +0 -0
  23. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/setup.cfg +0 -0
  24. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/setup.py +0 -0
  25. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/__init__.py +0 -0
  26. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/context.py +0 -0
  27. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/cypher_queries.yaml +0 -0
  28. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/load_files.yaml +0 -0
  29. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/movies.csv +0 -0
  30. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/people.csv +0 -0
  31. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/roles.csv +0 -0
  32. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test.py +0 -0
  33. {pyneoinstance-2.0.2 → pyneoinstance-2.2.0}/test/test_fileload.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyneoinstance
3
- Version: 2.0.2
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
 
@@ -1,4 +1,5 @@
1
1
  from .database.neo4jdbms import Neo4jInstance
2
2
  from .fileload.loadyaml import load_yaml_file
3
+ from .util.functions import get_parallel_info
3
4
 
4
- __version__ = '2.0.2'
5
+ __version__ = '2.2.0'
@@ -3,4 +3,4 @@ import os
3
3
  import sys
4
4
  sys.path.insert(0, os.path.abspath(
5
5
  os.path.join(os.path.dirname(__file__), '..')))
6
- from pyneoinstance.util.functions import get_logger
6
+ from pyneoinstance.util.functions import get_logger,get_parallel_info,get_columns_diff
@@ -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], data: DataFrame,
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.fillna(value='').to_dict('records'),
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 rows in data_chunks:
360
- rows_dict = {'rows': rows.fillna(value="").to_dict('records')}
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'],
@@ -1,3 +1,5 @@
1
1
  from .functions import get_file_name
2
2
  from .functions import is_reachable_url
3
3
  from .functions import get_logger
4
+ from .functions import get_parallel_info
5
+ from .functions import get_columns_diff
@@ -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
1
+ Metadata-Version: 2.4
2
2
  Name: pyneoinstance
3
- Version: 2.0.2
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.2"
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 TestGetFileName(unittest.TestCase):
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