galaxy_graph 0.0.2__py3-none-any.whl
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.
- galaxy_graph/__init__.py +11 -0
- galaxy_graph/connection.py +38 -0
- galaxy_graph/exception/__init__.py +9 -0
- galaxy_graph/exception/node_already_in_graph.py +17 -0
- galaxy_graph/exception/node_not_found.py +17 -0
- galaxy_graph/graph.py +99 -0
- galaxy_graph/node.py +99 -0
- galaxy_graph-0.0.2.dist-info/METADATA +10 -0
- galaxy_graph-0.0.2.dist-info/RECORD +10 -0
- galaxy_graph-0.0.2.dist-info/WHEEL +4 -0
galaxy_graph/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Connection class container."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from .node import Node
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Connection:
|
|
13
|
+
"""Connection between two nodes from the same graph."""
|
|
14
|
+
|
|
15
|
+
source: Node # First node on "node 1 connection node 2"
|
|
16
|
+
destination: Node # Last node on "node 1 connection node 2"
|
|
17
|
+
data: Any = None # Data attached to this connection
|
|
18
|
+
identifier: str = field(default_factory=lambda: str(uuid4()))
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def make(
|
|
22
|
+
cls,
|
|
23
|
+
node_1: Node,
|
|
24
|
+
node_2: Node,
|
|
25
|
+
data: Any = None,
|
|
26
|
+
) -> Connection:
|
|
27
|
+
"""Build a connection from its type."""
|
|
28
|
+
return cls(source=node_1, destination=node_2, data=data)
|
|
29
|
+
|
|
30
|
+
def __eq__(self, value: object, /) -> bool:
|
|
31
|
+
"""Check equality."""
|
|
32
|
+
if not isinstance(value, Connection):
|
|
33
|
+
raise TypeError
|
|
34
|
+
return self.identifier == value.identifier
|
|
35
|
+
|
|
36
|
+
def __hash__(self) -> int:
|
|
37
|
+
"""For built-in function hash()."""
|
|
38
|
+
return hash(self.identifier)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""NodeAlreadyInGraph class container."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NodeAlreadyInGraphError(Exception):
|
|
5
|
+
"""Node already in graph."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, identifier: str) -> None:
|
|
8
|
+
"""
|
|
9
|
+
Error initialization.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
identifier: str
|
|
14
|
+
Node identifier
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
super().__init__(f"Node {identifier} already in graph")
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""NodeNotFound class container."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NodeNotFoundError(Exception):
|
|
5
|
+
"""Node not found."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, identifier: str) -> None:
|
|
8
|
+
"""
|
|
9
|
+
Error initialization.
|
|
10
|
+
|
|
11
|
+
Parameters
|
|
12
|
+
----------
|
|
13
|
+
identifier: str
|
|
14
|
+
Node identifier
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
super().__init__(f"Node {identifier} not found")
|
galaxy_graph/graph.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Graph class container."""
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
from uuid import uuid4
|
|
7
|
+
|
|
8
|
+
from .exception import NodeAlreadyInGraphError, NodeNotFoundError
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
|
|
13
|
+
from .node import Node
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Graph:
|
|
18
|
+
"""Set of nodes."""
|
|
19
|
+
|
|
20
|
+
directed: bool # If false, connections will be shared by both nodes
|
|
21
|
+
nodes: set[Node] = field(default_factory=set) # Nodes in this graph
|
|
22
|
+
data: Any = None # Data attached to this graph
|
|
23
|
+
|
|
24
|
+
def add(
|
|
25
|
+
self,
|
|
26
|
+
class_: type[Node],
|
|
27
|
+
data: Any = None,
|
|
28
|
+
identifier: str | None = None,
|
|
29
|
+
) -> Graph:
|
|
30
|
+
"""
|
|
31
|
+
Add a node to this graph quickly.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
class_: type[Node]
|
|
36
|
+
Class of the Node to add
|
|
37
|
+
data: Any
|
|
38
|
+
Data of the Node to add
|
|
39
|
+
identifier: str | None
|
|
40
|
+
Identifier of the Node. Random identifier if None
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
if identifier is None:
|
|
44
|
+
identifier = str(uuid4())
|
|
45
|
+
return self.add_node(
|
|
46
|
+
class_(
|
|
47
|
+
graph=self,
|
|
48
|
+
data=data,
|
|
49
|
+
identifier=identifier,
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def add_node(self, node: Node) -> Graph:
|
|
54
|
+
"""Add a node to the graph, with all of its connections."""
|
|
55
|
+
try:
|
|
56
|
+
self.get_by_id(node.identifier)
|
|
57
|
+
raise NodeAlreadyInGraphError(node.identifier)
|
|
58
|
+
except NodeNotFoundError:
|
|
59
|
+
self.nodes.add(node)
|
|
60
|
+
for neighboor in node.get_neighbors():
|
|
61
|
+
with suppress(NodeAlreadyInGraphError):
|
|
62
|
+
self.add_node(neighboor)
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def get_by_id(self, node_id: str) -> Node:
|
|
66
|
+
"""Get a node from its node identifier."""
|
|
67
|
+
for node in self.nodes:
|
|
68
|
+
if node.identifier == node_id:
|
|
69
|
+
return node
|
|
70
|
+
raise NodeNotFoundError(node_id)
|
|
71
|
+
|
|
72
|
+
def get_by_class(self, class_: type[Node]) -> set[Node]:
|
|
73
|
+
"""Get all node of a given class."""
|
|
74
|
+
return {node for node in self.nodes if isinstance(node, class_)}
|
|
75
|
+
|
|
76
|
+
def get_by_data(self, filter_: Callable[[Any], bool]) -> set[Node]:
|
|
77
|
+
"""
|
|
78
|
+
Get all node of the graph respecting a condition on its data.
|
|
79
|
+
|
|
80
|
+
Return every nodes for which applying the node's data to filter
|
|
81
|
+
return True.
|
|
82
|
+
"""
|
|
83
|
+
return {node for node in self.nodes if filter_(node.data)}
|
|
84
|
+
|
|
85
|
+
def delete_node(self, node_id: str) -> Node:
|
|
86
|
+
"""Delete a node and all of its connections to other nodes."""
|
|
87
|
+
node = self.get_by_id(node_id=node_id)
|
|
88
|
+
|
|
89
|
+
self.nodes = {
|
|
90
|
+
node for node in self.nodes if node.identifier != node_id
|
|
91
|
+
}
|
|
92
|
+
for node in self.nodes:
|
|
93
|
+
node.connections = {
|
|
94
|
+
connection
|
|
95
|
+
for connection in node.connections
|
|
96
|
+
if connection.destination != node_id
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return node
|
galaxy_graph/node.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Node class container."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from uuid import uuid4
|
|
6
|
+
|
|
7
|
+
from .exception import NodeNotFoundError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .connection import Connection
|
|
11
|
+
from .graph import Graph
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class Node:
|
|
16
|
+
"""Singular element storing data."""
|
|
17
|
+
|
|
18
|
+
graph: Graph # Graph containing this Node
|
|
19
|
+
data: Any = None # Data attached to this node
|
|
20
|
+
identifier: str = field(default_factory=lambda: str(uuid4()))
|
|
21
|
+
connections: set[Connection] = field(default_factory=set)
|
|
22
|
+
|
|
23
|
+
def connect(
|
|
24
|
+
self,
|
|
25
|
+
connection: type[Connection],
|
|
26
|
+
node: Node,
|
|
27
|
+
data: Any = None,
|
|
28
|
+
) -> Connection:
|
|
29
|
+
"""
|
|
30
|
+
Connect this node to another one with a specific connection.
|
|
31
|
+
|
|
32
|
+
If the graph of the node is not directed, the other node will
|
|
33
|
+
have the same connection.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
connection: type[Connection]
|
|
38
|
+
Type of the connection
|
|
39
|
+
node: Node
|
|
40
|
+
Node to connect to
|
|
41
|
+
data: Any
|
|
42
|
+
Data of the connection
|
|
43
|
+
|
|
44
|
+
"""
|
|
45
|
+
try:
|
|
46
|
+
_ = self.graph.get_by_id(node_id=self.identifier)
|
|
47
|
+
except NodeNotFoundError:
|
|
48
|
+
self.graph.add_node(node=self)
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
_ = self.graph.get_by_id(node_id=node.identifier)
|
|
52
|
+
except NodeNotFoundError:
|
|
53
|
+
self.graph.add_node(node=node)
|
|
54
|
+
|
|
55
|
+
new_connection = connection.make(self, node, data)
|
|
56
|
+
|
|
57
|
+
self.connections.add(new_connection)
|
|
58
|
+
|
|
59
|
+
if not self.graph.directed:
|
|
60
|
+
node.connections.add(connection.make(node, self, data))
|
|
61
|
+
|
|
62
|
+
return new_connection
|
|
63
|
+
|
|
64
|
+
def get_neighbors(
|
|
65
|
+
self,
|
|
66
|
+
connection_type: type[Connection] | None = None,
|
|
67
|
+
) -> set[Node]:
|
|
68
|
+
"""
|
|
69
|
+
Get nodes linked to this node by at least one connection.
|
|
70
|
+
|
|
71
|
+
This can be filtered by the type of the connection, if
|
|
72
|
+
connection_type is not None.
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
connection_type: type[Connection] | None
|
|
77
|
+
Type on the connection to filter. Every connections if None
|
|
78
|
+
|
|
79
|
+
"""
|
|
80
|
+
if connection_type is None:
|
|
81
|
+
return {
|
|
82
|
+
connection.destination
|
|
83
|
+
for connection in self.connections
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
connection.destination
|
|
87
|
+
for connection in self.connections
|
|
88
|
+
if isinstance(connection, connection_type)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
def __eq__(self, value: object, /) -> bool:
|
|
92
|
+
"""Check equality."""
|
|
93
|
+
if not isinstance(value, Node):
|
|
94
|
+
raise TypeError
|
|
95
|
+
return self.identifier == value.identifier
|
|
96
|
+
|
|
97
|
+
def __hash__(self) -> int:
|
|
98
|
+
"""For built-in function hash()."""
|
|
99
|
+
return hash(self.identifier)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: galaxy_graph
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Graph creation and management
|
|
5
|
+
Author-email: linarphy <linarphy@linarphy.net>
|
|
6
|
+
Classifier: Development Status :: 3 - Alpha
|
|
7
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.14
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
galaxy_graph/__init__.py,sha256=Et8GkiD5I_dcdjFgd2X4jNZMOHiJnD9feiZCS8Ajixw,187
|
|
2
|
+
galaxy_graph/connection.py,sha256=gNQN1Br0vo4rt89iRQ5tb-KouHAHFx5DPzv4bLGxsD4,1090
|
|
3
|
+
galaxy_graph/graph.py,sha256=1N-kF0SNCZTu5zV3pCVILWvgjsKL8cnTksKTUdMuqCs,2987
|
|
4
|
+
galaxy_graph/node.py,sha256=tgVRKTpNYTCb2cPvKbhdxFVOm_lnBShKSZQ_xiuFmCk,2753
|
|
5
|
+
galaxy_graph/exception/__init__.py,sha256=MASGYxthFcmAeL5tGBNP6L3duMN0i51gZTVjew3FZec,209
|
|
6
|
+
galaxy_graph/exception/node_already_in_graph.py,sha256=k5sNKbmb9bM5GT3O3P-Ss7yr2KfqXlsqHCMP6MW5yig,379
|
|
7
|
+
galaxy_graph/exception/node_not_found.py,sha256=9wWzizelv3kG4tFqjeKy3Ni0su6sjnO7aArXCnhB8QA,353
|
|
8
|
+
galaxy_graph-0.0.2.dist-info/METADATA,sha256=k29ARaVxDzVza5rPBP9bC9NHyKqarg9fQdU_k1YJntY,392
|
|
9
|
+
galaxy_graph-0.0.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
10
|
+
galaxy_graph-0.0.2.dist-info/RECORD,,
|