graphitedb 0.1.2__tar.gz → 0.1.3__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphitedb
3
- Version: 0.1.2
4
- Summary: A clean graph database engine
3
+ Version: 0.1.3
4
+ Summary: A clean, embedded graph database engine for Python.
5
5
  Author-email: Mahan Khalili <khalili1388mahan@gmail.com>
6
6
  Maintainer-email: Mahan Khalili <khalili1388mahan@gmail.com>
7
7
  License-Expression: MIT
@@ -201,5 +201,3 @@ def example_complete_dsl_loading():
201
201
  ```
202
202
 
203
203
  More examples are available in `example.py` in the GitHub repository.
204
- ::contentReference[oaicite:0]{index=0}
205
- ```
@@ -185,5 +185,3 @@ def example_complete_dsl_loading():
185
185
  ```
186
186
 
187
187
  More examples are available in `example.py` in the GitHub repository.
188
- ::contentReference[oaicite:0]{index=0}
189
- ```
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "graphitedb"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  authors = [
9
9
  { name="Mahan Khalili", email="khalili1388mahan@gmail.com" },
10
10
  ]
11
11
  maintainers = [
12
12
  { name="Mahan Khalili", email="khalili1388mahan@gmail.com" },
13
13
  ]
14
- description = "A clean graph database engine"
14
+ description = "A clean, embedded graph database engine for Python."
15
15
  readme = "README.md"
16
16
  requires-python = ">=3.9"
17
17
  classifiers = [
@@ -1,3 +1,9 @@
1
+ """
2
+ Graphite: A clean, embedded graph database engine for Python.
3
+
4
+ This is graphite module (installation: ``pip install graphitedb``).
5
+ You can use it with ``import graphite``.
6
+ """
1
7
  from __future__ import annotations
2
8
 
3
9
  import pickle
@@ -11,6 +17,9 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
11
17
  # =============== TYPE SYSTEM ===============
12
18
 
13
19
  class DataType(Enum):
20
+ """
21
+ Valid data types in graphite. Used in nodes and relations properties.
22
+ """
14
23
  STRING = "string"
15
24
  INT = "int"
16
25
  DATE = "date"
@@ -19,12 +28,20 @@ class DataType(Enum):
19
28
 
20
29
  @dataclass
21
30
  class Field:
31
+ """
32
+ A data field (property) for nodes and relations.
33
+ """
22
34
  name: str
23
35
  dtype: DataType
24
36
  default: Any = None
25
37
 
26
38
  @dataclass
27
39
  class NodeType:
40
+ """
41
+ A defined node type (with ``node ...`` block in dsl or ``GraphiteEngine.define_node()``).
42
+ Each node type has a name (in snake_case usually), and optional list of fields (properties).
43
+ Supports optional parent node type.
44
+ """
28
45
  name: str
29
46
  fields: List[Field] = field(default_factory=list)
30
47
  parent: Optional[NodeType] = None
@@ -41,6 +58,12 @@ class NodeType:
41
58
 
42
59
  @dataclass
43
60
  class RelationType:
61
+ """
62
+ A defined relation type (with ``relation ...`` block in dsl or
63
+ ``GraphiteEngine.define_relation()``). Each relation type has a name (in UPPER_SNAKE_CASE
64
+ usually), and optional list of fields (properties). A relation type can be from one node
65
+ type to another.
66
+ """
44
67
  name: str
45
68
  from_type: str
46
69
  to_type: str
@@ -55,12 +78,17 @@ class RelationType:
55
78
 
56
79
  @dataclass
57
80
  class Node:
81
+ """
82
+ A node in database. Has a base type, id, and properties from base type (and it's parent
83
+ type recursively).
84
+ """
58
85
  type_name: str
59
86
  id: str
60
87
  values: Dict[str, Any]
61
88
  _type_ref: Optional[NodeType] = None
62
89
 
63
90
  def get(self, field_name: str) -> Any:
91
+ """Get a field from this node."""
64
92
  return self.values.get(field_name)
65
93
 
66
94
  def __getitem__(self, key):
@@ -71,6 +99,10 @@ class Node:
71
99
 
72
100
  @dataclass
73
101
  class Relation:
102
+ """
103
+ A relation between two nodes in database. Has a base type, source and target node IDs,
104
+ and properties from base type.
105
+ """
74
106
  type_name: str
75
107
  from_node: str # node id
76
108
  to_node: str # node id
@@ -78,6 +110,7 @@ class Relation:
78
110
  _type_ref: Optional[RelationType] = None
79
111
 
80
112
  def get(self, field_name: str) -> Any:
113
+ """Get a field from this relation."""
81
114
  return self.values.get(field_name)
82
115
 
83
116
  def __repr__(self):
@@ -119,6 +152,7 @@ class GraphiteParser:
119
152
 
120
153
  return node_name, fields, parent
121
154
 
155
+ # pylint: disable=too-many-locals
122
156
  @staticmethod
123
157
  def parse_relation_definition(line: str) -> Tuple[str, str, str, List[Field], Optional[str], bool]:
124
158
  """Parse relation definition"""
@@ -241,8 +275,8 @@ class GraphiteParser:
241
275
  class QueryResult:
242
276
  """Represents a query result that can be chained"""
243
277
 
244
- def __init__(self, engine: GraphiteEngine, nodes: List[Node], edges: List[Relation] = None):
245
- self.engine = engine
278
+ def __init__(self, graph_engine: GraphiteEngine, nodes: List[Node], edges: List[Relation] = None):
279
+ self.engine = graph_engine
246
280
  self.nodes = nodes
247
281
  self.edges = edges or []
248
282
  self.current_relation: Optional[RelationType] = None
@@ -254,21 +288,22 @@ class QueryResult:
254
288
 
255
289
  if callable(condition):
256
290
  # Lambda function
257
- for node in self.nodes:
291
+ for processing_node in self.nodes:
258
292
  try:
259
- if condition(node):
260
- filtered_nodes.append(node)
261
- except e:
262
- print(f"Graphite Warn: 'where' condition failed for node {node}: {e}")
293
+ if condition(processing_node):
294
+ filtered_nodes.append(processing_node)
295
+ except Exception as e: # pylint: disable=broad-exception-caught
296
+ print(f"Graphite Warn: 'where' condition failed for node {processing_node}: {e}")
263
297
  else:
264
298
  # String condition like "age > 18"
265
- for node in self.nodes:
266
- if self._evaluate_condition(node, condition):
267
- filtered_nodes.append(node)
299
+ for processing_node in self.nodes:
300
+ if self._evaluate_condition(processing_node, condition):
301
+ filtered_nodes.append(processing_node)
268
302
 
269
303
  return QueryResult(self.engine, filtered_nodes, self.edges)
270
304
 
271
- def _evaluate_condition(self, node: Node, condition: str) -> bool:
305
+ # pylint: disable=too-many-branches
306
+ def _evaluate_condition(self, target_node: Node, condition: str) -> bool:
272
307
  """Evaluate a condition string on a node"""
273
308
  # Simple condition parser
274
309
  ops = ['>=', '<=', '!=', '==', '>', '<', '=']
@@ -280,7 +315,7 @@ class QueryResult:
280
315
  right = right.strip()
281
316
 
282
317
  # Get value from node
283
- node_value = node.get(left)
318
+ node_value = target_node.get(left)
284
319
  if node_value is None:
285
320
  return False
286
321
 
@@ -295,18 +330,22 @@ class QueryResult:
295
330
  right_value = right
296
331
 
297
332
  # Apply operation
333
+ result = None
298
334
  if op in ('=', '=='):
299
- return node_value == right_value
300
- elif op == '!=':
301
- return node_value != right_value
302
- elif op == '>':
303
- return node_value > right_value
304
- elif op == '<':
305
- return node_value < right_value
306
- elif op == '>=':
307
- return node_value >= right_value
308
- elif op == '<=':
309
- return node_value <= right_value
335
+ result = node_value == right_value
336
+ if op == '!=':
337
+ result = node_value != right_value
338
+ if op == '>':
339
+ result = node_value > right_value
340
+ if op == '<':
341
+ result = node_value < right_value
342
+ if op == '>=':
343
+ result = node_value >= right_value
344
+ if op == '<=':
345
+ result = node_value <= right_value
346
+ if result is None:
347
+ raise ValueError(f"Invalid condition string: {condition}")
348
+ return result
310
349
 
311
350
  return False
312
351
 
@@ -315,14 +354,14 @@ class QueryResult:
315
354
  result_nodes = []
316
355
  result_edges = []
317
356
 
318
- for node in self.nodes:
357
+ for processing_node in self.nodes:
319
358
  if direction == 'outgoing':
320
- edges = self.engine.get_relations_from(node.id, relation_type)
359
+ edges = self.engine.get_relations_from(processing_node.id, relation_type)
321
360
  elif direction == 'incoming':
322
- edges = self.engine.get_relations_to(node.id, relation_type)
361
+ edges = self.engine.get_relations_to(processing_node.id, relation_type)
323
362
  else: # both
324
- edges = (self.engine.get_relations_from(node.id, relation_type) +
325
- self.engine.get_relations_to(node.id, relation_type))
363
+ edges = (self.engine.get_relations_from(processing_node.id, relation_type) +
364
+ self.engine.get_relations_to(processing_node.id, relation_type))
326
365
 
327
366
  for edge in edges:
328
367
  result_edges.append(edge)
@@ -355,17 +394,17 @@ class QueryResult:
355
394
  """Get distinct nodes"""
356
395
  seen = set()
357
396
  distinct_nodes = []
358
- for node in self.nodes:
359
- if node.id not in seen:
360
- seen.add(node.id)
361
- distinct_nodes.append(node)
397
+ for processing_node in self.nodes:
398
+ if processing_node.id not in seen:
399
+ seen.add(processing_node.id)
400
+ distinct_nodes.append(processing_node)
362
401
  return QueryResult(self.engine, distinct_nodes, self.edges)
363
402
 
364
- def order_by(self, field: str, descending: bool = False) -> QueryResult:
403
+ def order_by(self, by_field: str, descending: bool = False) -> QueryResult:
365
404
  """Order nodes by field"""
366
405
 
367
- def get_key(node):
368
- val = node.get(field)
406
+ def get_key(from_node):
407
+ val = from_node.get(by_field)
369
408
  return (val is None, val)
370
409
 
371
410
  sorted_nodes = sorted(self.nodes, key=get_key, reverse=descending)
@@ -387,11 +426,11 @@ class QueryResult:
387
426
  """Get node IDs"""
388
427
  return [n.id for n in self.nodes]
389
428
 
390
- class QueryBuilder:
429
+ class QueryBuilder: # pylint: disable=too-few-public-methods
391
430
  """Builder for creating queries"""
392
431
 
393
- def __init__(self, engine: GraphiteEngine):
394
- self.engine = engine
432
+ def __init__(self, graphite_engine: GraphiteEngine):
433
+ self.engine = graphite_engine
395
434
 
396
435
  def __getattr__(self, name: str) -> QueryResult:
397
436
  """Allow starting query from node type: engine.User"""
@@ -402,7 +441,7 @@ class QueryBuilder:
402
441
 
403
442
  # =============== MAIN ENGINE ===============
404
443
 
405
- class GraphiteEngine:
444
+ class GraphiteEngine: # pylint: disable=too-many-instance-attributes
406
445
  """Main graph database engine"""
407
446
 
408
447
  def __init__(self):
@@ -472,19 +511,19 @@ class GraphiteEngine:
472
511
 
473
512
  # Create values dictionary
474
513
  node_values = {}
475
- for field, value in zip(all_fields, values):
514
+ for current_field, value in zip(all_fields, values):
476
515
  # Convert string dates to date objects
477
- if field.dtype == DataType.DATE and isinstance(value, str):
516
+ if current_field.dtype == DataType.DATE and isinstance(value, str):
478
517
  try:
479
518
  value = datetime.strptime(value, "%Y-%m-%d").date()
480
- except e:
481
- raise ValueError(f"Invalid date format: {value}")
482
- node_values[field.name] = value
519
+ except Exception as e:
520
+ raise ValueError(f"'{e}' while parsing date string: {value}") from e
521
+ node_values[current_field.name] = value
483
522
 
484
- node = Node(node_type, node_id, node_values, node_type_obj)
485
- self.nodes[node_id] = node
486
- self.node_by_type[node_type].append(node)
487
- return node
523
+ new_node = Node(node_type, node_id, node_values, node_type_obj)
524
+ self.nodes[node_id] = new_node
525
+ self.node_by_type[node_type].append(new_node)
526
+ return new_node
488
527
 
489
528
  def create_relation(self, from_id: str, to_id: str, rel_type: str, *values) -> Relation:
490
529
  """Create a relation instance"""
@@ -501,21 +540,21 @@ class GraphiteEngine:
501
540
 
502
541
  # Create values dictionary
503
542
  rel_values = {}
504
- for i, field in enumerate(rel_type_obj.fields):
543
+ for i, rel_field in enumerate(rel_type_obj.fields):
505
544
  if i < len(values):
506
545
  value = values[i]
507
- if field.dtype == DataType.DATE and isinstance(value, str):
546
+ if rel_field.dtype == DataType.DATE and isinstance(value, str):
508
547
  try:
509
548
  value = datetime.strptime(value, "%Y-%m-%d").date()
510
- except e:
511
- raise ValueError(f"Invalid date format: {value}")
512
- rel_values[field.name] = value
549
+ except Exception as e:
550
+ raise ValueError(f"'{e}' while parsing date string: {value}") from e
551
+ rel_values[rel_field.name] = value
513
552
 
514
- relation = Relation(rel_type, from_id, to_id, rel_values, rel_type_obj)
515
- self.relations.append(relation)
516
- self.relations_by_type[rel_type].append(relation)
517
- self.relations_by_from[from_id].append(relation)
518
- self.relations_by_to[to_id].append(relation)
553
+ new_relation = Relation(rel_type, from_id, to_id, rel_values, rel_type_obj)
554
+ self.relations.append(new_relation)
555
+ self.relations_by_type[rel_type].append(new_relation)
556
+ self.relations_by_from[from_id].append(new_relation)
557
+ self.relations_by_to[to_id].append(new_relation)
519
558
 
520
559
  # If relation is bidirectional, create reverse automatically
521
560
  if rel_type_obj.is_bidirectional:
@@ -525,7 +564,7 @@ class GraphiteEngine:
525
564
  self.relations_by_from[to_id].append(reverse_rel)
526
565
  self.relations_by_to[from_id].append(reverse_rel)
527
566
 
528
- return relation
567
+ return new_relation
529
568
 
530
569
  # =============== QUERY METHODS ===============
531
570
 
@@ -568,7 +607,11 @@ class GraphiteEngine:
568
607
  # Collect multiline node definition
569
608
  node_def = [line]
570
609
  i += 1
571
- while i < len(lines) and lines[i].strip() and not lines[i].strip().startswith(('node', 'relation')):
610
+ while (
611
+ i < len(lines)
612
+ and lines[i].strip()
613
+ and not lines[i].strip().startswith(('node', 'relation'))
614
+ ):
572
615
  node_def.append(lines[i])
573
616
  i += 1
574
617
  self.define_node('\n'.join(node_def))
@@ -577,7 +620,11 @@ class GraphiteEngine:
577
620
  # Collect multiline relation definition
578
621
  rel_def = [line]
579
622
  i += 1
580
- while i < len(lines) and lines[i].strip() and not lines[i].strip().startswith(('node', 'relation')):
623
+ while (
624
+ i < len(lines)
625
+ and lines[i].strip()
626
+ and not lines[i].strip().startswith(('node', 'relation'))
627
+ ):
581
628
  rel_def.append(lines[i])
582
629
  i += 1
583
630
  self.define_relation('\n'.join(rel_def))
@@ -590,7 +637,7 @@ class GraphiteEngine:
590
637
 
591
638
  elif '-[' in line and (']->' in line or ']-' in line):
592
639
  # Relation instance
593
- from_id, to_id, rel_type, values, direction = self.parser.parse_relation_instance(line)
640
+ from_id, to_id, rel_type, values, _ = self.parser.parse_relation_instance(line)
594
641
  self.create_relation(from_id, to_id, rel_type, *values)
595
642
  i += 1
596
643
  else:
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graphitedb
3
- Version: 0.1.2
4
- Summary: A clean graph database engine
3
+ Version: 0.1.3
4
+ Summary: A clean, embedded graph database engine for Python.
5
5
  Author-email: Mahan Khalili <khalili1388mahan@gmail.com>
6
6
  Maintainer-email: Mahan Khalili <khalili1388mahan@gmail.com>
7
7
  License-Expression: MIT
@@ -201,5 +201,3 @@ def example_complete_dsl_loading():
201
201
  ```
202
202
 
203
203
  More examples are available in `example.py` in the GitHub repository.
204
- ::contentReference[oaicite:0]{index=0}
205
- ```
@@ -1,7 +1,6 @@
1
1
  LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
- src/__init__.py
5
4
  src/graphite/__init__.py
6
5
  src/graphitedb.egg-info/PKG-INFO
7
6
  src/graphitedb.egg-info/SOURCES.txt
@@ -0,0 +1,6 @@
1
+ """
2
+ This is a placeholder for future tests.
3
+ """
4
+
5
+ def test_placeholder():
6
+ """Just a placeholder"""
File without changes
@@ -1,3 +0,0 @@
1
- import pytest
2
-
3
- pytest.skip("Test suite not implemented yet", allow_module_level=True)
File without changes
File without changes