pyneoinstance 1.1.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 (34) hide show
  1. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/MANIFEST.in +1 -0
  2. {pyneoinstance-1.1.2/pyneoinstance.egg-info → pyneoinstance-2.2.0}/PKG-INFO +84 -10
  3. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/README.md +79 -7
  4. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/__init__.py +2 -1
  5. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/database/context.py +1 -1
  6. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/database/neo4jdbms.py +407 -7
  7. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/util/__init__.py +2 -0
  8. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/util/functions.py +43 -0
  9. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0/pyneoinstance.egg-info}/PKG-INFO +84 -10
  10. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/SOURCES.txt +2 -0
  11. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/requires.txt +1 -0
  12. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/top_level.txt +1 -0
  13. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyproject.toml +4 -3
  14. pyneoinstance-2.2.0/setup.py +5 -0
  15. pyneoinstance-2.2.0/test/__init__.py +3 -0
  16. pyneoinstance-2.2.0/test/context.py +8 -0
  17. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/cypher_queries.yaml +1 -1
  18. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test_neo4jdb.py +90 -1
  19. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test_util_functions.py +25 -1
  20. pyneoinstance-1.1.2/setup.py +0 -3
  21. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/LICENSE +0 -0
  22. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/database/__init__.py +0 -0
  23. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/__init__.py +0 -0
  24. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/context.py +0 -0
  25. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/helpers.py +0 -0
  26. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance/fileload/loadyaml.py +0 -0
  27. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/pyneoinstance.egg-info/dependency_links.txt +0 -0
  28. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/setup.cfg +0 -0
  29. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/load_files.yaml +0 -0
  30. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/movies.csv +0 -0
  31. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/people.csv +0 -0
  32. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/roles.csv +0 -0
  33. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test.py +0 -0
  34. {pyneoinstance-1.1.2 → pyneoinstance-2.2.0}/test/test_fileload.py +0 -0
@@ -1,2 +1,3 @@
1
1
  include test/*.yaml
2
2
  include test/*.csv
3
+ recursive-exclude images
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyneoinstance
3
- Version: 1.1.2
4
- Summary: Handle Neo4j read and write transactions
3
+ Version: 2.2.0
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
7
7
  Version 3, 29 June 2007
@@ -688,19 +688,31 @@ License-File: LICENSE
688
688
  Requires-Dist: neo4j>=5.0.0
689
689
  Requires-Dist: pandas
690
690
  Requires-Dist: PyYAML
691
+ Requires-Dist: pyvis
692
+ Dynamic: license-file
691
693
 
692
694
  # PyNeoInstance Package
693
695
 
694
696
  ## What is it?
695
697
 
696
- PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
698
+ PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j and performing Exploratory Data Analysis (EDA) of your graph.
699
+
697
700
  It supports the following features:
698
701
 
699
- - __Read query__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
700
- - __Write query__: Submit a write Cypher query, with or without parameters, to update an existing database.
701
- - __Write queries__: Submit a list of Cypher queries to update an existing database.
702
- - __Write query with data__: Update a database based on a Pandas Data Frame and a Cypher query.
703
- - __Write queries with data__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
702
+ - __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
703
+ - __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
704
+ - __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
705
+ - __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
706
+ - __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
707
+ - __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
708
+ - __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
709
+ - __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
710
+ - __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
711
+ - __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
712
+ - __<u>Constraints</u>__: Get information of all constraints in the graph.
713
+ - __<u>Indexes</u>__: Get information of all indexes in the graph.
714
+ - __<u>Schema Visualization</u>__: Visualize the graph schema.
715
+
704
716
 
705
717
  ## How to install it?
706
718
 
@@ -921,7 +933,69 @@ person_load_result = graph.execute_write_query_with_data(
921
933
  workers = 5
922
934
  )
923
935
  ```
936
+ ## Graph EDA
937
+
938
+ ### Node Label Frequency
939
+ The get_node_label_freq method provides the frequency and relative frequency of all nodes in the graph. This method consider all node labels as single labels, if you want the distribution considering multiple labels use the get_node_multilabel_freq method.
940
+ ```python
941
+ graph.get_node_label_freq(database='mydbname')
942
+ ```
943
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
944
+
945
+ ### Relationship Type Frequency
946
+ The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
947
+ ```python
948
+ graph.get_rela_type_freq(database='mydbname')
949
+ ```
950
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
951
+
952
+ ### Relationship Source-Target Frequency
953
+ The get_rela_source_target_freq method provides the frequency and relative frequency of all relationship types considering the node label of the source and targets of the relationships.
954
+ ```python
955
+ graph.get_rela_type_freq(database='mydbname')
956
+ ```
957
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
958
+
959
+ ### Properties
960
+ The get_properties method provides information about all the nodes and relationships properties.
961
+ ```python
962
+ graph.graph.get_properties(database='mydbname')
963
+ ```
964
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
965
+
966
+ ### Constraints
967
+ The get_constraints method provides information about all the graph constraints.
968
+ ```python
969
+ graph.get_constraints(database='mydbname')
970
+ ```
971
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
972
+
973
+ ### Indexes
974
+ The get_indexes method provides information about all the graph indexes.
975
+ ```python
976
+ graph.get_indexes(database='mydbname')
977
+ ```
978
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
979
+
980
+ ### Schema Visualization
981
+ The graph.get_schema_visualization provides an interactive UI with the visualization of the graph schema with the ability to filter the schema. This UI can be access via a Notebook or HTML page. This visualization displays the node labels as string close to nodes, relationship types are only display on mouse hover.
982
+
983
+ ```python
984
+ schema = graph.get_schema_visualization(database='mydbname')
985
+ schema.show('schema.html')
986
+ ```
987
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
988
+
989
+ To access the UI via HTTML page just set the parameter notebook to false in the show method and open the html file in your browser.
990
+
991
+ ```python
992
+ schema = graph.get_schema_visualization(database='mydbname')
993
+ schema.show('schema.html',notebook=False)
994
+ ```
995
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
924
996
 
925
997
  ## More detailed examples
926
998
 
927
- For more detailed examples please referred to this [article](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
999
+ For more detailed examples please refer to the following articles:
1000
+ * Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
1001
+ * Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
@@ -2,14 +2,24 @@
2
2
 
3
3
  ## What is it?
4
4
 
5
- PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
5
+ PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j and performing Exploratory Data Analysis (EDA) of your graph.
6
+
6
7
  It supports the following features:
7
8
 
8
- - __Read query__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
9
- - __Write query__: Submit a write Cypher query, with or without parameters, to update an existing database.
10
- - __Write queries__: Submit a list of Cypher queries to update an existing database.
11
- - __Write query with data__: Update a database based on a Pandas Data Frame and a Cypher query.
12
- - __Write queries with data__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
9
+ - __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
10
+ - __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
11
+ - __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
12
+ - __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
13
+ - __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
14
+ - __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
15
+ - __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
16
+ - __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
17
+ - __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
18
+ - __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
19
+ - __<u>Constraints</u>__: Get information of all constraints in the graph.
20
+ - __<u>Indexes</u>__: Get information of all indexes in the graph.
21
+ - __<u>Schema Visualization</u>__: Visualize the graph schema.
22
+
13
23
 
14
24
  ## How to install it?
15
25
 
@@ -230,7 +240,69 @@ person_load_result = graph.execute_write_query_with_data(
230
240
  workers = 5
231
241
  )
232
242
  ```
243
+ ## Graph EDA
244
+
245
+ ### Node Label Frequency
246
+ The get_node_label_freq method provides the frequency and relative frequency of all nodes in the graph. This method consider all node labels as single labels, if you want the distribution considering multiple labels use the get_node_multilabel_freq method.
247
+ ```python
248
+ graph.get_node_label_freq(database='mydbname')
249
+ ```
250
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
251
+
252
+ ### Relationship Type Frequency
253
+ The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
254
+ ```python
255
+ graph.get_rela_type_freq(database='mydbname')
256
+ ```
257
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
258
+
259
+ ### Relationship Source-Target Frequency
260
+ The get_rela_source_target_freq method provides the frequency and relative frequency of all relationship types considering the node label of the source and targets of the relationships.
261
+ ```python
262
+ graph.get_rela_type_freq(database='mydbname')
263
+ ```
264
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
265
+
266
+ ### Properties
267
+ The get_properties method provides information about all the nodes and relationships properties.
268
+ ```python
269
+ graph.graph.get_properties(database='mydbname')
270
+ ```
271
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
272
+
273
+ ### Constraints
274
+ The get_constraints method provides information about all the graph constraints.
275
+ ```python
276
+ graph.get_constraints(database='mydbname')
277
+ ```
278
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
279
+
280
+ ### Indexes
281
+ The get_indexes method provides information about all the graph indexes.
282
+ ```python
283
+ graph.get_indexes(database='mydbname')
284
+ ```
285
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
286
+
287
+ ### Schema Visualization
288
+ The graph.get_schema_visualization provides an interactive UI with the visualization of the graph schema with the ability to filter the schema. This UI can be access via a Notebook or HTML page. This visualization displays the node labels as string close to nodes, relationship types are only display on mouse hover.
289
+
290
+ ```python
291
+ schema = graph.get_schema_visualization(database='mydbname')
292
+ schema.show('schema.html')
293
+ ```
294
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
295
+
296
+ To access the UI via HTTML page just set the parameter notebook to false in the show method and open the html file in your browser.
297
+
298
+ ```python
299
+ schema = graph.get_schema_visualization(database='mydbname')
300
+ schema.show('schema.html',notebook=False)
301
+ ```
302
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
233
303
 
234
304
  ## More detailed examples
235
305
 
236
- For more detailed examples please referred to this [article](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
306
+ For more detailed examples please refer to the following articles:
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)
@@ -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__ = '1.1.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
@@ -13,13 +13,16 @@ from typing import Optional, List, Dict, Any
13
13
  from collections import defaultdict
14
14
  import multiprocessing as mp
15
15
  import logging
16
+ import re
16
17
  import numpy as np
17
18
  from pandas import DataFrame
18
19
  from neo4j import GraphDatabase
20
+ from pyvis.network import Network
19
21
  from neo4j.exceptions import ServiceUnavailable
20
22
  from neo4j.exceptions import AuthError
21
23
  from neo4j.exceptions import ClientError
22
24
  from neo4j.exceptions import ConfigurationError
25
+ from .context import get_logger, get_parallel_info, get_columns_diff
23
26
 
24
27
  def _get_driver(neo_info : Dict[str, str]):
25
28
  try:
@@ -167,8 +170,12 @@ class Neo4jInstance:
167
170
  self.neo_info['password'] = password
168
171
  self.neo_info['encrypted'] = kwargs.get('encrypted') or ''
169
172
  self.__results = defaultdict(int)
170
-
171
-
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)
172
179
  def execute_read_query(self, query: str,
173
180
  database: Optional[str] = None,
174
181
  parameters: Optional[Dict[str, Any]] = None
@@ -288,7 +295,8 @@ class Neo4jInstance:
288
295
  result = self.execute_write_queries([query], database, parameters)
289
296
  return result
290
297
 
291
- 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,
292
300
  database: Optional[str] = None,
293
301
  partitions: Optional[int] = 1,
294
302
  parallel: Optional[bool] = False,
@@ -334,7 +342,7 @@ class Neo4jInstance:
334
342
  if partitions > data.shape[0]:
335
343
  raise ValueError(
336
344
  "The batch size cannot be greater than the number of rows in the data.")
337
- data_chunks = np.array_split(data,partitions)
345
+ data_chunks = np.array_split(data.index,partitions)
338
346
  if parameters:
339
347
  params = parameters
340
348
  else:
@@ -345,19 +353,27 @@ class Neo4jInstance:
345
353
  else:
346
354
  workers_num = mp.cpu_count()
347
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)
348
360
  with mp.Pool(workers_num) as worker_pool:
349
361
  results = worker_pool.starmap(
350
362
  _execute_write_parallel,
351
- [(self.neo_info, database,query, d.fillna(value='').to_dict('records'),
363
+ [(self.neo_info, database,query, data.iloc[d].to_dict('records'),
352
364
  params) for d in data_chunks]
353
365
  )
354
366
  else:
355
367
  results = []
356
368
  with _get_driver(self.neo_info) as driver:
357
369
  with _get_session(driver, database) as session:
358
- for rows in data_chunks:
359
- 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')}
360
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)
361
377
  results.append(_execute_write(session,
362
378
  query,
363
379
  rows_dict['rows'],
@@ -421,3 +437,387 @@ class Neo4jInstance:
421
437
  result = self.execute_write_queries_with_data(
422
438
  [query], data,database, partitions, parallel, workers, params)
423
439
  return result
440
+
441
+ def get_node_label_freq(self, database: Optional[str] = None) -> DataFrame:
442
+ """Use to obtain the graph node label frequency.
443
+
444
+ Parameters
445
+ ----------
446
+ database : str, optional
447
+ Name of the Neo4j database of which to execute the transaction.
448
+ If not provided the default database is going to be use.
449
+
450
+ Returns
451
+ -------
452
+ DataFrame
453
+ Pandas DataFrame object containing the frequency and relative
454
+ frequency of node labels.
455
+
456
+ Raises
457
+ ------
458
+ ClientError
459
+ When APOC Library is not install correctly in your Neo4j
460
+ deployment.
461
+ """
462
+ error_msg = """
463
+ APOC Library not detected.
464
+ Please install it before procceding.
465
+ """
466
+ query = """
467
+ MATCH(n)
468
+ WITH count(*) AS nodeCount
469
+ CALL db.labels() YIELD label
470
+ CALL apoc.cypher.run('MATCH (:`'+label+'`) RETURN count(*) as freq',{}) YIELD value
471
+ WITH nodeCount,label,value.freq AS freq
472
+ WITH *, 10^3 AS scaleFactor, toFloat(freq)/toFloat(nodeCount) AS relFreq
473
+ RETURN label AS nodeLabel,
474
+ freq AS frequency,
475
+ round(relFreq*scaleFactor)/scaleFactor AS relativeFrequency
476
+ ORDER BY freq DESC
477
+ """
478
+ with _get_driver(self.neo_info) as driver:
479
+ with _get_session(driver, database) as session:
480
+ try:
481
+ result = session.execute_read(
482
+ _read_transaction_function,query=query)
483
+ except ServiceUnavailable as exception:
484
+ raise ServiceUnavailable() from exception
485
+ except ClientError:
486
+ raise ClientError(error_msg)
487
+ return result
488
+
489
+ def get_node_multilabel_freq(self, database: Optional[str] = None) -> DataFrame:
490
+ """Use to obtain the graph node multi-label frequency.
491
+
492
+ Parameters
493
+ ----------
494
+ database : str, optional
495
+ Name of the Neo4j database of which to execute the transaction.
496
+ If not provided the default database is going to be use.
497
+
498
+ Returns
499
+ -------
500
+ DataFrame
501
+ Pandas DataFrame object containing the frequency and relative
502
+ frequency of node with multiple labels.
503
+
504
+ Raises
505
+ ------
506
+ ClientError
507
+ When APOC Library is not install correctly in your Neo4j
508
+ deployment.
509
+ """
510
+ error_msg = """
511
+ APOC Library not detected.
512
+ Please install it before procceding.
513
+ """
514
+ query = """
515
+ MATCH (n)
516
+ WITH labels(n) as nodeLabels
517
+ WHERE size(nodeLabels)>1
518
+ RETURN nodeLabels, count(*) as frequency
519
+ """
520
+ with _get_driver(self.neo_info) as driver:
521
+ with _get_session(driver, database) as session:
522
+ try:
523
+ result = session.execute_read(
524
+ _read_transaction_function,query=query)
525
+ except ServiceUnavailable as exception:
526
+ raise ServiceUnavailable() from exception
527
+ except ClientError:
528
+ raise ClientError(error_msg)
529
+ return result
530
+
531
+ def get_rela_type_freq(self, database: Optional[str] = None) -> DataFrame:
532
+ """Use to obtain the graph relationship type frequency.
533
+
534
+ Parameters
535
+ ----------
536
+ database : str, optional
537
+ Name of the Neo4j database of which to execute the transaction.
538
+ If not provided the default database is going to be use.
539
+
540
+ Returns
541
+ -------
542
+ DataFrame
543
+ Pandas DataFrame object containing the frequency and relative
544
+ frequency of relationship types.
545
+
546
+ Raises
547
+ ------
548
+ ClientError
549
+ When APOC Library is not install correctly in your Neo4j
550
+ deployment.
551
+ """
552
+ error_msg = """
553
+ APOC Library not detected.
554
+ Please install it before procceding.
555
+ """
556
+ query = """
557
+ MATCH()-[]->()
558
+ WITH count(*) AS relCount
559
+ CALL db.relationshipTypes() YIELD relationshipType as type
560
+ CALL apoc.cypher.run('MATCH ()-[:`'+type+'`]->() RETURN count(*) as freq',{})
561
+ YIELD value
562
+ WITH type AS relationshipType, value.freq AS freq,relCount
563
+ WITH *,3 AS presicion
564
+ WITH *, 10^presicion AS factor,toFloat(freq)/toFloat(relCount) as relFreq
565
+ RETURN relationshipType, freq AS frequency,
566
+ round(relFreq*factor)/factor AS relativeFrequency
567
+ ORDER BY freq DESC;
568
+ """
569
+ with _get_driver(self.neo_info) as driver:
570
+ with _get_session(driver, database) as session:
571
+ try:
572
+ result = session.execute_read(
573
+ _read_transaction_function,query=query)
574
+ except ServiceUnavailable as exception:
575
+ raise ServiceUnavailable() from exception
576
+ except ClientError:
577
+ raise ClientError(error_msg)
578
+ return result
579
+
580
+ def get_properties(self, database: Optional[str] = None) -> DataFrame:
581
+ """Use to obtain the node and relationship properties.
582
+
583
+ Parameters
584
+ ----------
585
+ database : str, optional
586
+ Name of the Neo4j database of which to execute the transaction.
587
+ If not provided the default database is going to be use.
588
+
589
+ Returns
590
+ -------
591
+ DataFrame
592
+ Pandas DataFrame object containing information about all nodes
593
+ and relationships properties.
594
+
595
+ Raises
596
+ ------
597
+ ClientError
598
+ When APOC Library is not install correctly in your Neo4j
599
+ deployment.
600
+ """
601
+ error_msg = """
602
+ APOC Library not detected.
603
+ Please install it before procceding.
604
+ """
605
+ query = """
606
+ CALL apoc.meta.data() YIELD label,property,type,elementType
607
+ WHERE type<>'RELATIONSHIP'
608
+ RETURN elementType,label,property,type
609
+ ORDER BY elementType,label,property;
610
+ """
611
+ with _get_driver(self.neo_info) as driver:
612
+ with _get_session(driver, database) as session:
613
+ try:
614
+ result = session.execute_read(
615
+ _read_transaction_function,query=query)
616
+ except ServiceUnavailable as exception:
617
+ raise ServiceUnavailable() from exception
618
+ except ClientError:
619
+ raise ClientError(error_msg)
620
+ return result
621
+
622
+ def get_constraints(self, database: Optional[str] = None) -> DataFrame:
623
+ """Use to obtain the constraints in the graph.
624
+
625
+ Parameters
626
+ ----------
627
+ database : str, optional
628
+ Name of the Neo4j database of which to execute the transaction.
629
+ If not provided the default database is going to be use.
630
+
631
+ Returns
632
+ -------
633
+ DataFrame
634
+ Pandas DataFrame object containing information about all the
635
+ constraints.
636
+
637
+ Raises
638
+ ------
639
+ ClientError
640
+ When APOC Library is not install correctly in your Neo4j
641
+ deployment.
642
+ """
643
+ error_msg = """
644
+ APOC Library not detected.
645
+ Please install it before procceding.
646
+ """
647
+ query = """
648
+ SHOW CONSTRAINTS
649
+ """
650
+ with _get_driver(self.neo_info) as driver:
651
+ with _get_session(driver, database) as session:
652
+ try:
653
+ result = session.execute_read(
654
+ _read_transaction_function,query=query)
655
+ except ServiceUnavailable as exception:
656
+ raise ServiceUnavailable() from exception
657
+ except ClientError:
658
+ raise ClientError(error_msg)
659
+ return result
660
+
661
+ def get_indexes(self, database: Optional[str] = None) -> DataFrame:
662
+ """Use to obtain the indexes in the graph.
663
+
664
+ Parameters
665
+ ----------
666
+ database : str, optional
667
+ Name of the Neo4j database of which to execute the transaction.
668
+ If not provided the default database is going to be use.
669
+
670
+ Returns
671
+ -------
672
+ DataFrame
673
+ Pandas DataFrame object containing information about all the
674
+ indexes.
675
+
676
+ Raises
677
+ ------
678
+ ClientError
679
+ When APOC Library is not install correctly in your Neo4j
680
+ deployment.
681
+ """
682
+ error_msg = """
683
+ APOC Library not detected.
684
+ Please install it before procceding.
685
+ """
686
+ query = """
687
+ SHOW INDEX
688
+ """
689
+ with _get_driver(self.neo_info) as driver:
690
+ with _get_session(driver, database) as session:
691
+ try:
692
+ result = session.execute_read(
693
+ _read_transaction_function,query=query)
694
+ except ServiceUnavailable as exception:
695
+ raise ServiceUnavailable() from exception
696
+ except ClientError:
697
+ raise ClientError(error_msg)
698
+ return result
699
+
700
+ def get_rela_source_target_freq(self, database: Optional[str] = None) -> DataFrame:
701
+ """Use to obtain the relationships source and target frequency.
702
+
703
+ Parameters
704
+ ----------
705
+ database : str, optional
706
+ Name of the Neo4j database of which to execute the transaction.
707
+ If not provided the default database is going to be use.
708
+
709
+ Returns
710
+ -------
711
+ DataFrame
712
+ Pandas DataFrame object containing the frequency of
713
+ relationships cource and target frequency.
714
+
715
+ Raises
716
+ ------
717
+ ClientError
718
+ When APOC Library is not install correctly in your Neo4j
719
+ deployment.
720
+ """
721
+ error_msg = """
722
+ APOC Library not detected.
723
+ Please install it before procceding.
724
+ """
725
+ query = """
726
+ CALL apoc.meta.stats() YIELD relTypes
727
+ """
728
+ with _get_driver(self.neo_info) as driver:
729
+ with _get_session(driver, database) as session:
730
+ try:
731
+ result = session.execute_read(
732
+ _read_transaction_function,query=query)
733
+ except ServiceUnavailable as exception:
734
+ raise ServiceUnavailable() from exception
735
+ except ClientError:
736
+ raise ClientError(error_msg)
737
+ stat_dict = result.iloc[0,0]
738
+ stats_dicts = []
739
+ for key,value in stat_dict.items():
740
+ nodes = re.findall(r"\(:?(\w*)\)",key)
741
+ relationship = re.findall(r"\[:?(\w*)\]",key)[0]
742
+ info = {'sourceLabel':nodes[0],'relationshipType':relationship,'targetLabel':nodes[1],
743
+ 'frequency':value}
744
+ stats_dicts.append(info)
745
+ stats_df = DataFrame(stats_dicts)
746
+ stats_df.sort_values(by=['relationshipType','sourceLabel','targetLabel'],inplace=True)
747
+ return stats_df
748
+
749
+ def get_schema_visualization(self, database: Optional[str] = None) -> Network:
750
+ """Use to visualize the graph data model (schema).
751
+
752
+ Parameters
753
+ ----------
754
+ database : str, optional
755
+ Name of the Neo4j database of which to execute the transaction.
756
+ If not provided the default database is going to be use.
757
+
758
+ Returns
759
+ -------
760
+ Network
761
+ Pyvis Network object containing the visualization information.
762
+
763
+ Raises
764
+ ------
765
+ ClientError
766
+ When APOC Library is not install correctly in your Neo4j
767
+ deployment.
768
+ """
769
+ error_msg = """
770
+ APOC Library not detected.
771
+ Please install it before procceding.
772
+ """
773
+ query = """
774
+ CALL apoc.meta.schema() YIELD value
775
+ """
776
+ with _get_driver(self.neo_info) as driver:
777
+ with _get_session(driver, database) as session:
778
+ try:
779
+ result = session.execute_read(
780
+ _read_transaction_function,query=query)
781
+ except ServiceUnavailable as exception:
782
+ raise ServiceUnavailable() from exception
783
+ except ClientError:
784
+ raise ClientError(error_msg)
785
+ schema = result.iloc[0,0]
786
+ network = Network(notebook=True,cdn_resources = "remote",directed=True,
787
+ filter_menu=True,height="800px", width="100%")
788
+ options = """
789
+ const options = {
790
+ "physics": {
791
+ "barnesHut": {
792
+ "gravitationalConstant": -13950,
793
+ "centralGravity": 6.15,
794
+ "springLength": 160,
795
+ "damping": 0.23
796
+ },
797
+ "minVelocity": 0.75
798
+ }
799
+ }
800
+ """
801
+ network.set_options(options)
802
+ nodes = []
803
+ relationships = set()
804
+ for key in schema.keys():
805
+ if schema[key]['type']=='node':
806
+ nodes.append(key)
807
+ network.add_nodes(nodes)
808
+ for node in nodes:
809
+ for rel in schema[node]['relationships'].keys():
810
+ title = rel
811
+ if schema[node]['relationships'][rel]['direction']=='in':
812
+ target = node
813
+ for label in schema[node]['relationships'][rel]['labels']:
814
+ source = label
815
+ else:
816
+ source = node
817
+ for label in schema[node]['relationships'][rel]['labels']:
818
+ target = label
819
+ relationships.add(source + "|" + title + "|" + target)
820
+ for relationship in relationships:
821
+ rela_parts = relationship.split("|")
822
+ network.add_edge(rela_parts[0],rela_parts[2],title=rela_parts[1])
823
+ return network
@@ -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,7 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyneoinstance
3
- Version: 1.1.2
4
- Summary: Handle Neo4j read and write transactions
3
+ Version: 2.2.0
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
7
7
  Version 3, 29 June 2007
@@ -688,19 +688,31 @@ License-File: LICENSE
688
688
  Requires-Dist: neo4j>=5.0.0
689
689
  Requires-Dist: pandas
690
690
  Requires-Dist: PyYAML
691
+ Requires-Dist: pyvis
692
+ Dynamic: license-file
691
693
 
692
694
  # PyNeoInstance Package
693
695
 
694
696
  ## What is it?
695
697
 
696
- PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j.
698
+ PyNeoInstance is a Python package that provides a user friendly API for submitting Cypher queries to Neo4j and performing Exploratory Data Analysis (EDA) of your graph.
699
+
697
700
  It supports the following features:
698
701
 
699
- - __Read query__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
700
- - __Write query__: Submit a write Cypher query, with or without parameters, to update an existing database.
701
- - __Write queries__: Submit a list of Cypher queries to update an existing database.
702
- - __Write query with data__: Update a database based on a Pandas Data Frame and a Cypher query.
703
- - __Write queries with data__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
702
+ - __<u>Read query</u>__: Submit a Cypher read query, with or without parameters, and get a Pandas Data Frame with the results.
703
+ - __<u>Write query</u>__: Submit a write Cypher query, with or without parameters, to update an existing database.
704
+ - __<u>Write queries</u>__: Submit a list of Cypher queries to update an existing database.
705
+ - __<u>Write query with data</u>__: Update a database based on a Pandas Data Frame and a Cypher query.
706
+ - __<u>Write queries with data</u>__: Update a database based on a Pandas Data Frame and a list of Cypher queries.
707
+ - __<u>Node label frequency</u>__: Get the distribution of the graph node labels.
708
+ - __<u>Multi label node frequency</u>__: Get the distribution of the graph multi label nodes.
709
+ - __<u>Relationship type frequency</u>__: Get the distribution of the graph relationship types.
710
+ - __<u>Relationship type source-target frequency</u>__: Get the distribution of relationship source and target.
711
+ - __<u>Node and relationship properties</u>__: Get information of all properties in the graph.
712
+ - __<u>Constraints</u>__: Get information of all constraints in the graph.
713
+ - __<u>Indexes</u>__: Get information of all indexes in the graph.
714
+ - __<u>Schema Visualization</u>__: Visualize the graph schema.
715
+
704
716
 
705
717
  ## How to install it?
706
718
 
@@ -921,7 +933,69 @@ person_load_result = graph.execute_write_query_with_data(
921
933
  workers = 5
922
934
  )
923
935
  ```
936
+ ## Graph EDA
937
+
938
+ ### Node Label Frequency
939
+ The get_node_label_freq method provides the frequency and relative frequency of all nodes in the graph. This method consider all node labels as single labels, if you want the distribution considering multiple labels use the get_node_multilabel_freq method.
940
+ ```python
941
+ graph.get_node_label_freq(database='mydbname')
942
+ ```
943
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/nodefreq.png" width="250">
944
+
945
+ ### Relationship Type Frequency
946
+ The get_rela_type_freq method provides the frequency and relative frequency of all relationship types in the graph.
947
+ ```python
948
+ graph.get_rela_type_freq(database='mydbname')
949
+ ```
950
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaFreq.png" width="300">
951
+
952
+ ### Relationship Source-Target Frequency
953
+ The get_rela_source_target_freq method provides the frequency and relative frequency of all relationship types considering the node label of the source and targets of the relationships.
954
+ ```python
955
+ graph.get_rela_type_freq(database='mydbname')
956
+ ```
957
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/relaSourceTargetFreq.png" width="300">
958
+
959
+ ### Properties
960
+ The get_properties method provides information about all the nodes and relationships properties.
961
+ ```python
962
+ graph.graph.get_properties(database='mydbname')
963
+ ```
964
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/properties.png" width="350" hight="300">
965
+
966
+ ### Constraints
967
+ The get_constraints method provides information about all the graph constraints.
968
+ ```python
969
+ graph.get_constraints(database='mydbname')
970
+ ```
971
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/constraints.png" width="350" hight="300">
972
+
973
+ ### Indexes
974
+ The get_indexes method provides information about all the graph indexes.
975
+ ```python
976
+ graph.get_indexes(database='mydbname')
977
+ ```
978
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/indexes.png" width="450" hight="400">
979
+
980
+ ### Schema Visualization
981
+ The graph.get_schema_visualization provides an interactive UI with the visualization of the graph schema with the ability to filter the schema. This UI can be access via a Notebook or HTML page. This visualization displays the node labels as string close to nodes, relationship types are only display on mouse hover.
982
+
983
+ ```python
984
+ schema = graph.get_schema_visualization(database='mydbname')
985
+ schema.show('schema.html')
986
+ ```
987
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schema.png" width="450" hight="400">
988
+
989
+ To access the UI via HTTML page just set the parameter notebook to false in the show method and open the html file in your browser.
990
+
991
+ ```python
992
+ schema = graph.get_schema_visualization(database='mydbname')
993
+ schema.show('schema.html',notebook=False)
994
+ ```
995
+ <img src="https://s3.amazonaws.com/neo4j-ps-ds-bootcamp/img/schemaHtml.png" width="450" hight="400">
924
996
 
925
997
  ## More detailed examples
926
998
 
927
- For more detailed examples please referred to this [article](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
999
+ For more detailed examples please refer to the following articles:
1000
+ * Data ingestion: [PyNeoInstance: A User-Friendly Python Library for Neo4j](https://medium.com/@a-gilmore/pyneoinstance-a-user-friendly-python-library-for-neo4j-dbefa3117bb2).
1001
+ * Graph EDA: [Performing graph EDA with the PyNeoInstance Python Package](https://medium.com/@jose.alvarado-guzman/0c0f6f230aa9)
@@ -18,6 +18,8 @@ pyneoinstance/fileload/helpers.py
18
18
  pyneoinstance/fileload/loadyaml.py
19
19
  pyneoinstance/util/__init__.py
20
20
  pyneoinstance/util/functions.py
21
+ test/__init__.py
22
+ test/context.py
21
23
  test/cypher_queries.yaml
22
24
  test/load_files.yaml
23
25
  test/movies.csv
@@ -1,3 +1,4 @@
1
1
  neo4j>=5.0.0
2
2
  pandas
3
3
  PyYAML
4
+ pyvis
@@ -6,8 +6,8 @@ build-backend = "setuptools.build_meta"
6
6
 
7
7
  [project]
8
8
  name = "pyneoinstance"
9
- version = "1.1.2"
10
- description = "Handle Neo4j read and write transactions"
9
+ version = "2.2.0"
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" }]
13
13
  license = { file = "LICENSE" }
@@ -20,6 +20,7 @@ keywords = ["neo4j", "graph", "cypher"]
20
20
  dependencies = [
21
21
  "neo4j>=5.0.0",
22
22
  "pandas",
23
- "PyYAML"
23
+ "PyYAML",
24
+ "pyvis"
24
25
  ]
25
26
  requires-python = ">=3.8"
@@ -0,0 +1,5 @@
1
+ import setuptools
2
+
3
+ setuptools.setup(
4
+ packages=setuptools.find_packages(exclude=("images"))
5
+ )
@@ -0,0 +1,3 @@
1
+ import os
2
+
3
+ test_dir = os.path.abspath(os.path.dirname(__file__))
@@ -0,0 +1,8 @@
1
+ """Use to provide context to the test module"""
2
+ import os
3
+ import sys
4
+ sys.path.insert(0, os.path.abspath(
5
+ os.path.join(os.path.dirname(__file__), '..')))
6
+ from pyneoinstance import fileload
7
+ from pyneoinstance import util
8
+ from pyneoinstance import database
@@ -28,7 +28,7 @@ queries:
28
28
  MATCH(p:Person {personId:row.personId}),(m:Movie {movieId:row.movieId})
29
29
  MERGE(p)-[r:ACTED_IN]->(m)
30
30
  ON CREATE
31
- SET r.character=split(row.character,':')
31
+ SET r.characters=split(row.characters,':')
32
32
  create_actor: |
33
33
  MERGE(p:Person:Actor {personId:$id})
34
34
  ON CREATE
@@ -9,6 +9,9 @@
9
9
  import os
10
10
  import unittest
11
11
  import pandas as pd
12
+ import numpy as np
13
+ import pandas.testing as pd_testing
14
+ from neo4j.exceptions import ClientError
12
15
  from .context import database
13
16
  from .context import fileload
14
17
  from . import test_dir
@@ -24,6 +27,12 @@ class TestNeo4jInstance(unittest.TestCase):
24
27
  test_neo4j_instance()
25
28
  Test all the methods in the Neo4jInstance class.
26
29
  """
30
+ def assertDataframeEqual(self, a, b, msg):
31
+ try:
32
+ pd_testing.assert_frame_equal(a, b)
33
+ except AssertionError as e:
34
+ raise self.failureException(msg) from e
35
+
27
36
  def setUp(self):
28
37
  """Set authentication variables and create Neo4j driver connection."""
29
38
  try:
@@ -37,10 +46,11 @@ class TestNeo4jInstance(unittest.TestCase):
37
46
  self.yaml_file = os.path.join(
38
47
  test_dir,'cypher_queries.yaml')
39
48
  self.queries = fileload.load_yaml_file(self.yaml_file,required_keys)
49
+ self.addTypeEqualityFunc(pd.DataFrame, self.assertDataframeEqual)
40
50
 
41
51
  def tearDown(self):
42
52
  """Close the neo4j driver and delete the test data."""
43
- queries = ["CALL apoc.schema.assert({},{})","MATCH(n) DELETE n"]
53
+ queries = ["CALL apoc.schema.assert({},{})","MATCH(n) DETACH DELETE n"]
44
54
  self.graph.execute_write_queries(queries, self.queries['database'])
45
55
 
46
56
  def test_neo4j_instance(self):
@@ -109,5 +119,84 @@ class TestNeo4jInstance(unittest.TestCase):
109
119
  'properties_set':4}
110
120
  self.assertEqual(solution, result)
111
121
 
122
+ # Test get_node_labels_freq
123
+ print('Get node labels freq')
124
+ role_file = os.path.join(test_dir,'roles.csv')
125
+ role_df = pd.read_csv(role_file).replace(np.NaN,'')
126
+ result = self.graph.get_node_label_freq(
127
+ self.queries['database'])
128
+ solution = pd.DataFrame([
129
+ {'nodeLabel':'Person','frequency':18727,'relativeFrequency':.75},
130
+ {'nodeLabel':'Movie','frequency':6231,'relativeFrequency':.25},
131
+ {'nodeLabel':'Actor','frequency':1,'relativeFrequency':.00}
132
+ ])
133
+ self.assertEqual(result,solution)
134
+
135
+ # Test get_node_multilabels_freq
136
+ print('Get node multilabel freq')
137
+ result = self.graph.get_node_multilabel_freq(
138
+ self.queries['database'])
139
+ solution = pd.DataFrame([
140
+ {'nodeLabels':['Person','Actor'],'frequency':1}])
141
+ self.assertEqual(result,solution)
142
+
143
+ # Test get_rela_labels_freq
144
+ print('Get relationship type freq')
145
+ self.graph.execute_write_query_with_data(
146
+ self.queries['queries']['load_role'],
147
+ role_df,
148
+ self.queries['database'])
149
+ result = self.graph.get_rela_type_freq(
150
+ self.queries['database'])
151
+ solution = pd.DataFrame([
152
+ {'relationshipType':'ACTED_IN','frequency':56914,'relativeFrequency':1.0}])
153
+ self.assertEqual(result,solution)
154
+
155
+ # Test get_properties
156
+ print('Get properties')
157
+ query = """
158
+ CALL apoc.meta.data() YIELD label,property,type,elementType
159
+ WHERE type<>'RELATIONSHIP'
160
+ RETURN elementType,label,property,type
161
+ ORDER BY elementType,label,property;
162
+ """
163
+ solution = self.graph.execute_read_query(query,self.queries['database'])
164
+ result = self.graph.get_properties(
165
+ self.queries['database'])
166
+ self.assertEqual(result,solution)
167
+
168
+ # Test constraints
169
+ print('Get constraints')
170
+ query = """
171
+ SHOW CONSTRAINTS
172
+ """
173
+ solution = self.graph.execute_read_query(query,self.queries['database'])
174
+ result = self.graph.get_constraints(
175
+ self.queries['database'])
176
+ self.assertEqual(result,solution)
177
+
178
+ # Test indexes
179
+ print('Get indexes')
180
+ query = """
181
+ SHOW INDEX
182
+ """
183
+ solution = self.graph.execute_read_query(query,self.queries['database'])
184
+ result = self.graph.get_indexes(
185
+ self.queries['database'])
186
+ self.assertEqual(result,solution)
187
+
188
+ # Test indexes
189
+ print('Get rela details')
190
+ result = self.graph.get_rela_source_target_freq(
191
+ self.queries['database'])
192
+ solution = pd.DataFrame([
193
+ {'sourceLabel':'','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914},
194
+ {'sourceLabel':'','relationshipType':'ACTED_IN','targetLabel':'Movie','frequency':56914},
195
+ {'sourceLabel':'Person','relationshipType':'ACTED_IN','targetLabel':'','frequency':56914}
196
+ ]
197
+ )
198
+ #solution.index = [2,0,1]
199
+ self.assertEqual(result,solution)
200
+
112
201
  if __name__ == '__main__':
113
202
  unittest.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)
@@ -1,3 +0,0 @@
1
- from setuptools import setup
2
-
3
- setup()
File without changes
File without changes