Skip to content

Commit c71250e

Browse files
authored
v4.0.0
2 parents d5a34a7 + 89ab45d commit c71250e

File tree

16 files changed

+2387
-739
lines changed

16 files changed

+2387
-739
lines changed

doc/DataStructures/Tree.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,13 @@ Features
109109
* Fast and simple tree data structure based on a single :py:class:`~pyTooling.Tree.Node` class.
110110
* A tree can be constructed top-down and bottom-up.
111111
* A node can have a unique ID.
112-
* Each node knows its level (distance from root).
112+
* A node knows its level (distance from root).
113113
* A node can have a value.
114114
* A node can store key-value-pairs via dictionary syntax.
115115
* A node has a reference to its parent node.
116-
* Each node has a reference to the root node in a tree (representative node).
116+
* A node has a reference to the root node in a tree (representative node).
117+
* Rendering to simple ASCII art for debugging purposes.
118+
117119

118120
.. _STRUCT/Tree/MissingFeatures:
119121

@@ -131,8 +133,9 @@ Missing Features
131133
Planned Features
132134
================
133135

134-
* Rendering to simple ASCII art for debugging purposes.
135136
* Allow filters (predicates) in generators to allow node filtering.
137+
* Tree export to formats like GraphML, ...
138+
* Export the tree data structure to file the YAML format.
136139
* Allow nodes to have tags and group nodes by tags.
137140
* Allow nodes to link to other nodes (implement proxy behavior?)
138141

@@ -144,9 +147,9 @@ Out of Scope
144147

145148
* Preserve or recover the tree data structure before an erroneous operation caused an exception and aborted a tree
146149
modification, which might leave the tree in a corrupted state.
147-
* Export the tree data structure to various file formats like JSON, YAML, TOML, ...
150+
* Export the tree data structure to various file formats like JSON, TOML, ...
148151
* Import a tree data structure from various file formats like JSON, YAML, TOML, ...
149-
* Tree visualization or rendering to complex formats like GraphML, GraphViz, Mermaid, ...
152+
* Tree visualization or rendering to complex formats like GraphViz, Mermaid, ...
150153

151154

152155
.. _STRUCT/Tree/ByFeature:

doc/Dependency.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ the mandatory dependencies too.
125125
+-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
126126
| **Package** | **Version** | **License** | **Dependencies** |
127127
+=================================================================================================+==============+==========================================================================================================+======================================================================================================================================================+
128-
| `pyTooling <https://GitHub.com/pyTooling/pyTooling>`__ |2.13.0 | `Apache License, 2.0 <https://GitHub.com/pyTooling/pyTooling/blob/main/LICENSE.md>`__ | *None* |
128+
| `pyTooling <https://GitHub.com/pyTooling/pyTooling>`__ |3.0.0 | `Apache License, 2.0 <https://GitHub.com/pyTooling/pyTooling/blob/main/LICENSE.md>`__ | *None* |
129129
+-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
130130
| `Sphinx <https://GitHub.com/sphinx-doc/sphinx>`__ | ≥5.3.0 | `BSD 3-Clause <https://GitHub.com/sphinx-doc/sphinx/blob/master/LICENSE>`__ | *Not yet evaluated.* |
131131
+-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
@@ -166,7 +166,7 @@ install the mandatory dependencies too.
166166
+----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
167167
| **Package** | **Version** | **License** | **Dependencies** |
168168
+============================================================================+==============+==========================================================================================================+======================================================================================================================================================+
169-
| `pyTooling <https://GitHub.com/pyTooling/pyTooling>`__ |2.13.0 | `Apache License, 2.0 <https://GitHub.com/pyTooling/pyTooling/blob/main/LICENSE.md>`__ | *None* |
169+
| `pyTooling <https://GitHub.com/pyTooling/pyTooling>`__ |3.0.0 | `Apache License, 2.0 <https://GitHub.com/pyTooling/pyTooling/blob/main/LICENSE.md>`__ | *None* |
170170
+----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+
171171
| `wheel <https://GitHub.com/pypa/wheel>`__ | ≥0.38.1 | `MIT <https://github.com/pypa/wheel/blob/main/LICENSE.txt>`__ | *Not yet evaluated.* |
172172
+----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+

doc/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-r ../requirements.txt
22

3-
pyTooling>=2.13.0, <3.0
3+
pyTooling>=3.0.0, <4.0
44

55
# Enforce latest version on ReadTheDocs
66
sphinx >=5.3, <6.0

pyTooling/Common/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@
3737
__email__ = "Paebbels@gmail.com"
3838
__copyright__ = "2017-2022, Patrick Lehmann"
3939
__license__ = "Apache License, Version 2.0"
40-
__version__ = "3.0.0"
40+
__version__ = "4.0.0"
4141
__keywords__ = ["decorators", "meta-classes", "exceptions", "platform", "versioning", "licensing", "overloading",
4242
"singleton", "tree", "graph", "timer", "data structure", "setuptools", "wheel", "installation",
43-
"packaging", "path", "generic path", "generic library", "url"]
43+
"packaging", "path", "generic path", "generic library", "url", "terminal", "shell", "TUI", "console",
44+
"text user interface", "message logging", "abstract", "override"]
4445

4546
from collections import deque
4647
from functools import reduce

pyTooling/Graph/GraphML.py

Lines changed: 136 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@
3737
"""
3838
from enum import Enum, auto
3939
from pathlib import Path
40-
from typing import Any, List, Dict
40+
from typing import Any, List, Dict, Union, Optional as Nullable
4141

4242
from pyTooling.Decorators import export
4343
from pyTooling.MetaClasses import ExtendedType
44-
from pyTooling.Graph import Graph as pyToolingGraph
44+
from pyTooling.Graph import Graph as pyToolingGraph, Subgraph as pyToolingSubgraph
4545
from pyTooling.Tree import Node as pyToolingNode
4646

4747

@@ -289,7 +289,7 @@ def ToStringLines(self, indent: int = 2) -> List[str]:
289289

290290

291291
@export
292-
class Graph(BaseWithData):
292+
class BaseGraph(BaseWithData):
293293
_subgraphs: Dict[str, 'Subgraph']
294294
_nodes: Dict[str, Node]
295295
_edges: Dict[str, Edge]
@@ -340,6 +340,9 @@ def AddEdge(self, edge: Edge) -> Edge:
340340
self._edges[edge._id] = edge
341341
return edge
342342

343+
def GetEdge(self, edgeName: str) -> Edge:
344+
return self._edges[edgeName]
345+
343346
def OpeningTag(self, indent: int = 1) -> str:
344347
return f"""\
345348
{' '*indent}<graph id="{self._id}"
@@ -368,13 +371,49 @@ def ToStringLines(self, indent: int = 1) -> List[str]:
368371

369372

370373
@export
371-
class Subgraph(Node, Graph):
374+
class Graph(BaseGraph):
375+
_document: 'GraphMLDocument'
376+
_ids: Dict[str, Union[Node, Edge, 'Subgraph']]
377+
378+
def __init__(self, document: 'GraphMLDocument', identifier: str):
379+
super().__init__(identifier)
380+
self._document = document
381+
self._ids = {}
382+
383+
def GetByID(self, identifier: str) -> Union[Node, Edge, 'Subgraph']:
384+
return self._ids[identifier]
385+
386+
def AddSubgraph(self, subgraph: 'Subgraph') -> 'Subgraph':
387+
result = super().AddSubgraph(subgraph)
388+
self._ids[subgraph._subgraphID] = subgraph
389+
subgraph._root = self
390+
return result
391+
392+
def AddNode(self, node: Node) -> Node:
393+
result = super().AddNode(node)
394+
self._ids[node._id] = node
395+
return result
396+
397+
def AddEdge(self, edge: Edge) -> Edge:
398+
result = super().AddEdge(edge)
399+
self._ids[edge._id] = edge
400+
return result
401+
402+
403+
@export
404+
class Subgraph(Node, BaseGraph):
372405
_subgraphID: str
406+
_root: Nullable[Graph]
373407

374408
def __init__(self, nodeIdentifier: str, graphIdentifier: str):
375409
super().__init__(nodeIdentifier)
376410

377411
self._subgraphID = graphIdentifier
412+
self._root = None
413+
414+
@property
415+
def RootGraph(self) -> Graph:
416+
return self._root
378417

379418
@property
380419
def SubgraphID(self) -> str:
@@ -384,6 +423,16 @@ def SubgraphID(self) -> str:
384423
def HasClosingTag(self) -> bool:
385424
return True
386425

426+
def AddNode(self, node: Node) -> Node:
427+
result = super().AddNode(node)
428+
self._root._ids[node._id] = node
429+
return result
430+
431+
def AddEdge(self, edge: Edge) -> Edge:
432+
result = super().AddEdge(edge)
433+
self._root._ids[edge._id] = edge
434+
return result
435+
387436
def Tag(self, indent: int = 2) -> str:
388437
raise NotImplementedError()
389438

@@ -399,7 +448,7 @@ def OpeningTag(self, indent: int = 1) -> str:
399448
"""
400449

401450
def ClosingTag(self, indent: int = 2) -> str:
402-
return Graph.ClosingTag(self, indent)
451+
return BaseGraph.ClosingTag(self, indent)
403452

404453
def ToStringLines(self, indent: int = 2) -> List[str]:
405454
lines = [super().OpeningTag(indent)]
@@ -435,11 +484,11 @@ class GraphMLDocument(Base):
435484
def __init__(self, identifier: str = "G"):
436485
super().__init__()
437486

438-
self._graph = Graph(identifier)
487+
self._graph = Graph(self, identifier)
439488
self._keys = {}
440489

441490
@property
442-
def Graph(self) -> Graph:
491+
def Graph(self) -> BaseGraph:
443492
return self._graph
444493

445494
@property
@@ -457,37 +506,91 @@ def HasKey(self, keyName: str) -> bool:
457506
return keyName in self._keys
458507

459508
def FromGraph(self, graph: pyToolingGraph):
509+
document = self
460510
self._graph._id = graph._name
461511

462512
nodeValue = self.AddKey(Key("nodeValue", AttributeContext.Node, "value", AttributeTypes.String))
463513
edgeValue = self.AddKey(Key("edgeValue", AttributeContext.Edge, "value", AttributeTypes.String))
464514

465-
for vertex in graph.IterateVertices():
466-
newNode = Node(vertex._id)
467-
newNode.AddData(Data(nodeValue, vertex._value))
468-
for key, value in vertex._dict.items():
469-
if self.HasKey(str(key)):
470-
nodeKey = self.GetKey(f"node{key!s}")
471-
else:
472-
nodeKey = self.AddKey(Key(f"node{key!s}", AttributeContext.Node, str(key), AttributeTypes.String))
473-
newNode.AddData(Data(nodeKey, value))
474-
475-
self._graph.AddNode(newNode)
476-
477-
for edge in graph.IterateEdges():
478-
source = self._graph.GetNode(edge._source._id)
479-
target = self._graph.GetNode(edge._destination._id)
480-
481-
newEdge = Edge(edge._id, source, target)
482-
newEdge.AddData(Data(edgeValue, edge._value))
483-
for key, value in edge._dict.items():
484-
if self.HasKey(str(key)):
485-
edgeKey = self.GetKey(f"edge{key!s}")
486-
else:
487-
edgeKey = self.AddKey(Key(f"edge{key!s}", AttributeContext.Edge, str(key), AttributeTypes.String))
488-
newEdge.AddData(Data(edgeKey, value))
489-
490-
self._graph.AddEdge(newEdge)
515+
def translateGraph(rootGraph: Graph, pyTGraph: pyToolingGraph):
516+
for vertex in pyTGraph.IterateVertices():
517+
newNode = Node(vertex._id)
518+
newNode.AddData(Data(nodeValue, vertex._value))
519+
for key, value in vertex._dict.items():
520+
if document.HasKey(str(key)):
521+
nodeKey = document.GetKey(f"node{key!s}")
522+
else:
523+
nodeKey = document.AddKey(Key(f"node{key!s}", AttributeContext.Node, str(key), AttributeTypes.String))
524+
newNode.AddData(Data(nodeKey, value))
525+
526+
rootGraph.AddNode(newNode)
527+
528+
for edge in pyTGraph.IterateEdges():
529+
source = rootGraph.GetByID(edge._source._id)
530+
target = rootGraph.GetByID(edge._destination._id)
531+
532+
newEdge = Edge(edge._id, source, target)
533+
newEdge.AddData(Data(edgeValue, edge._value))
534+
for key, value in edge._dict.items():
535+
if self.HasKey(str(key)):
536+
edgeKey = self.GetBy(f"edge{key!s}")
537+
else:
538+
edgeKey = self.AddKey(Key(f"edge{key!s}", AttributeContext.Edge, str(key), AttributeTypes.String))
539+
newEdge.AddData(Data(edgeKey, value))
540+
541+
rootGraph.AddEdge(newEdge)
542+
543+
for link in pyTGraph.IterateLinks():
544+
source = rootGraph.GetByID(link._source._id)
545+
target = rootGraph.GetByID(link._destination._id)
546+
547+
newEdge = Edge(link._id, source, target)
548+
newEdge.AddData(Data(edgeValue, link._value))
549+
for key, value in link._dict.items():
550+
if self.HasKey(str(key)):
551+
edgeKey = self.GetKey(f"link{key!s}")
552+
else:
553+
edgeKey = self.AddKey(Key(f"link{key!s}", AttributeContext.Edge, str(key), AttributeTypes.String))
554+
newEdge.AddData(Data(edgeKey, value))
555+
556+
rootGraph.AddEdge(newEdge)
557+
558+
def translateSubgraph(nodeGraph: Subgraph, pyTSubgraph: pyToolingSubgraph):
559+
rootGraph = nodeGraph.RootGraph
560+
561+
for vertex in pyTSubgraph.IterateVertices():
562+
newNode = Node(vertex._id)
563+
newNode.AddData(Data(nodeValue, vertex._value))
564+
for key, value in vertex._dict.items():
565+
if self.HasKey(str(key)):
566+
nodeKey = self.GetKey(f"node{key!s}")
567+
else:
568+
nodeKey = self.AddKey(Key(f"node{key!s}", AttributeContext.Node, str(key), AttributeTypes.String))
569+
newNode.AddData(Data(nodeKey, value))
570+
571+
nodeGraph.AddNode(newNode)
572+
573+
for edge in pyTSubgraph.IterateEdges():
574+
source = nodeGraph.GetNode(edge._source._id)
575+
target = nodeGraph.GetNode(edge._destination._id)
576+
577+
newEdge = Edge(edge._id, source, target)
578+
newEdge.AddData(Data(edgeValue, edge._value))
579+
for key, value in edge._dict.items():
580+
if self.HasKey(str(key)):
581+
edgeKey = self.GetKey(f"edge{key!s}")
582+
else:
583+
edgeKey = self.AddKey(Key(f"edge{key!s}", AttributeContext.Edge, str(key), AttributeTypes.String))
584+
newEdge.AddData(Data(edgeKey, value))
585+
586+
nodeGraph.AddEdge(newEdge)
587+
588+
for subgraph in graph.Subgraphs:
589+
nodeGraph = Subgraph(subgraph.Name, "sg" + subgraph.Name)
590+
self._graph.AddSubgraph(nodeGraph)
591+
translateSubgraph(nodeGraph, subgraph)
592+
593+
translateGraph(self._graph, graph)
491594

492595
def FromTree(self, tree: pyToolingNode):
493596
self._graph._id = tree._id

0 commit comments

Comments
 (0)