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.
@@ -0,0 +1,11 @@
1
+ """Graph creation and management library."""
2
+
3
+ from .connection import Connection
4
+ from .graph import Graph
5
+ from .node import Node
6
+
7
+ __all__ = [
8
+ "Connection",
9
+ "Graph",
10
+ "Node",
11
+ ]
@@ -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,9 @@
1
+ """Exception of this module."""
2
+
3
+ from .node_already_in_graph import NodeAlreadyInGraphError
4
+ from .node_not_found import NodeNotFoundError
5
+
6
+ __all__ = [
7
+ "NodeAlreadyInGraphError",
8
+ "NodeNotFoundError",
9
+ ]
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any