Coverage for /var/devmt/py/docp-dbi_1.1.0/docp_dbi/libs/utilities.py: 100%
51 statements
« prev ^ index » next coverage.py v7.8.0, created at 2026-04-20 13:13 +0100
« prev ^ index » next coverage.py v7.8.0, created at 2026-04-20 13:13 +0100
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3"""
4:Purpose: This module provides generalised utility-based functionality
5 for the project.
7:Platform: Linux/Windows | Python 3.11+
8:Developer: J Berendt
9:Email: development@s3dev.uk
11:Comments: n/a
13"""
15from __future__ import annotations
17import csv
18import os
19# locals
20try:
21 from ..objects.neo4jobjects import Node, Edge
22except ImportError:
23 from docp_dbi.objects.neo4jobjects import Node, Edge
26class Utilities:
27 """Generalised project-based utilities."""
29 @staticmethod
30 def csv_to_nodes(path: str) -> list[Node]:
31 """Create a list of :class:`Node` objects from CSV file.
33 Args:
34 path (str): Full path to the input file.
36 .. important::
38 The CSV file is expected to have the following field names:
39 - id
40 - label
42 Raises:
43 FileNotFoundError: Raised if the provided file path does not
44 exist.
46 Returns:
47 list[Node]: A list of :class:`Node` objects, created from the
48 CSV file.
50 """
51 if not os.path.exists(path):
52 raise FileNotFoundError(f'Source file not found: {path}')
53 nodes = []
54 with open(path, encoding='utf-8') as f:
55 for rec in csv.DictReader(f):
56 id_ = rec.pop('id')
57 label = rec.pop('label')
58 nodes.append(Node(id=id_, label=label, properties=rec))
59 return nodes
61 @staticmethod
62 def csv_to_edges(path: str) -> list[Edge]:
63 """Create a list of :class:`Edge` objects from CSV file.
65 Args:
66 path (str): Full path to the input file.
68 .. important::
70 The CSV file is expected to have the following field names:
71 - source
72 - target
73 - type
75 Raises:
76 FileNotFoundError: Raised if the provided file path does not
77 exist.
79 Returns:
80 list[Edge]: A list of :class:`Edge` objects, created from
81 the CSV file.
83 """
84 # pylint: disable=redefined-builtin # Needed here.
85 if not os.path.exists(path):
86 raise FileNotFoundError(f'Source file not found: {path}')
87 edges = []
88 with open(path, encoding='utf-8') as f:
89 for rec in csv.DictReader(f):
90 source = rec.pop('source')
91 target = rec.pop('target')
92 type_ = rec.pop('type')
93 edges.append(Edge(source=source,
94 target=target,
95 type=type_,
96 properties=rec))
97 return edges
99 def merge_node_query(self, node: Node) -> str: # noqa # pylint: disable=undefined-variable
100 """Build a MERGE node query.
102 Args:
103 node (Node): Node object for which the query is to be built.
105 Returns:
106 str: A MERGE Cypher query for adding a new node to the graph.
108 """
109 if node.properties:
110 return self._node_query_with_properties(node=node)
111 return self._node_query(node=node) # nocover
113 def merge_edge_query(self, edge: Edge) -> str: # noqa # pylint: disable=undefined-variable
114 """Build a MERGE edge (relationship) query.
116 Args:
117 edge (Edge): Edge object for which the query is to be built.
119 Returns:
120 str: A MERGE Cypher query for adding a new edge (relationship)
121 to the graph.
123 """
124 if edge.properties:
125 return self._edge_query_with_properties(edge=edge)
126 return self._edge_query(edge=edge)
128 @staticmethod
129 def _node_query(node: Node) -> str: # noqa # pylint: disable=undefined-variable # nocover
130 """Build a MERGE node query."""
131 q = f"""
132 MERGE (n:{node.label} { id: {node.id} } )
133 RETURN n
134 """
135 return q.strip()
137 @staticmethod
138 def _node_query_with_properties(node: Node) -> str: # noqa # pylint: disable=undefined-variable
139 """Build a MERGE node query, for a node with properties."""
140 # pylint: disable=consider-using-f-string # Nope. Need this.
141 properties_ = ', '.join(map('{0}: ${0}'.format, node.properties))
142 q = f"""
143 MERGE (n:{node.label} { id: {node.id} } )
144 SET n += { {properties_} }
145 RETURN n
146 """
147 return q.strip()
149 @staticmethod
150 def _edge_query(edge: Edge) -> str: # noqa # pylint: disable=undefined-variable
151 """Build a MERGE edge query."""
152 q = f"""
153 MATCH (s { id: {edge.source.id} } )
154 MATCH (t { id: {edge.target.id} } )
155 MERGE (s)-[e:{edge.type}]-(t)
156 RETURN e
157 """
158 return q.strip()
160 @staticmethod
161 def _edge_query_with_properties(edge: Edge) -> str: # noqa # pylint: disable=undefined-variable
162 """Build a MERGE edge query, for an edge with properties."""
163 # pylint: disable=consider-using-f-string # Nope. Need this.
164 properties_ = ', '.join(map('{0}: ${0}'.format, edge.properties))
165 q = f"""
166 MATCH (s { id: {edge.source.id} } )
167 MATCH (t { id: {edge.target.id} } )
168 MERGE (s)-[e:{edge.type}]-(t)
169 SET e += { { properties_} }
170 RETURN e
171 """
172 return q.strip()
175utilities = Utilities()