Skip to content

Commit 0bb0075

Browse files
authored
ref(dependency-graph): Adding more context to the dependency graph (#248)
* ref(dependency-graph): Adding more context to the dependency graph * Removing comment * Removing unused function * Removing redundant logic * Removing redundant function * Adding cycle guard
1 parent b0d15d3 commit 0bb0075

File tree

4 files changed

+437
-60
lines changed

4 files changed

+437
-60
lines changed

devservices/commands/down.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from devservices.utils.console import Console
2323
from devservices.utils.console import Status
2424
from devservices.utils.dependencies import construct_dependency_graph
25+
from devservices.utils.dependencies import DependencyNode
26+
from devservices.utils.dependencies import DependencyType
2527
from devservices.utils.dependencies import get_non_shared_remote_dependencies
2628
from devservices.utils.dependencies import install_and_verify_dependencies
2729
from devservices.utils.dependencies import InstalledRemoteDependency
@@ -234,7 +236,10 @@ def _get_dependent_service(
234236
)
235237
# If the service we are trying to bring down is in the dependency graph of another service,
236238
# we should not bring it down
237-
if service.name in dependency_graph.graph:
239+
if (
240+
DependencyNode(name=service.name, dependency_type=DependencyType.SERVICE)
241+
in dependency_graph.graph
242+
):
238243
return other_started_service
239244

240245
return None

devservices/commands/up.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
from devservices.utils.console import Console
2525
from devservices.utils.console import Status
2626
from devservices.utils.dependencies import construct_dependency_graph
27+
from devservices.utils.dependencies import DependencyNode
28+
from devservices.utils.dependencies import DependencyType
2729
from devservices.utils.dependencies import install_and_verify_dependencies
2830
from devservices.utils.dependencies import InstalledRemoteDependency
2931
from devservices.utils.docker import check_all_containers_healthy
@@ -169,7 +171,12 @@ def _up(
169171
dependency_graph = construct_dependency_graph(service, modes=modes)
170172
starting_order = dependency_graph.get_starting_order()
171173
sorted_remote_dependencies = sorted(
172-
remote_dependencies, key=lambda dep: starting_order.index(dep.service_name)
174+
remote_dependencies,
175+
key=lambda dep: starting_order.index(
176+
DependencyNode(
177+
name=dep.service_name, dependency_type=DependencyType.SERVICE
178+
)
179+
),
173180
)
174181
# Pull all images in parallel
175182
status.info("Pulling images")

devservices/utils/dependencies.py

+54-28
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from concurrent.futures import as_completed
1111
from concurrent.futures import ThreadPoolExecutor
1212
from dataclasses import dataclass
13+
from enum import Enum
1314
from typing import TextIO
1415
from typing import TypeGuard
1516

@@ -53,59 +54,73 @@
5354
]
5455

5556

57+
class DependencyType(str, Enum):
58+
SERVICE = "service"
59+
COMPOSE = "compose"
60+
61+
62+
@dataclass(frozen=True, eq=True)
63+
class DependencyNode:
64+
name: str
65+
dependency_type: DependencyType
66+
67+
def __str__(self) -> str:
68+
return self.name
69+
70+
5671
class DependencyGraph:
5772
def __init__(self) -> None:
58-
self.graph: dict[str, set[str]] = dict()
73+
self.graph: dict[DependencyNode, set[DependencyNode]] = dict()
5974

60-
def add_dependency(self, service_name: str) -> None:
61-
if service_name not in self.graph:
62-
self.graph[service_name] = set()
75+
def add_node(self, node: DependencyNode) -> None:
76+
if node not in self.graph:
77+
self.graph[node] = set()
6378

64-
def add_edge(self, service_name: str, dependency_name: str) -> None:
65-
# TODO: We should rename services that depend on themselves
66-
if service_name == dependency_name:
67-
return
68-
if service_name not in self.graph:
69-
self.add_dependency(service_name)
70-
if dependency_name not in self.graph:
71-
self.add_dependency(dependency_name)
79+
def add_edge(self, from_node: DependencyNode, to_node: DependencyNode) -> None:
80+
if from_node == to_node:
81+
# TODO: Add a better exception
82+
raise ValueError("Cannot add an edge from a node to itself")
83+
if from_node not in self.graph:
84+
self.add_node(from_node)
85+
if to_node not in self.graph:
86+
self.add_node(to_node)
7287

7388
# TODO: Should we check for cycles here?
7489

75-
self.graph[service_name].add(dependency_name)
90+
self.graph[from_node].add(to_node)
7691

77-
def topological_sort(self) -> list[str]:
92+
def topological_sort(self) -> list[DependencyNode]:
7893
in_degree = {service_name: 0 for service_name in self.graph}
7994

80-
for service_name in self.graph.keys():
81-
for dependency in self.graph[service_name]:
82-
in_degree[dependency] += 1
95+
for service_node in self.graph.keys():
96+
for dependency_node in self.graph[service_node]:
97+
in_degree[dependency_node] += 1
8398

8499
queue = deque(
85100
[
86-
service_name
87-
for service_name in self.graph
88-
if in_degree[service_name] == 0
101+
dependency_node
102+
for dependency_node in self.graph
103+
if in_degree[dependency_node] == 0
89104
]
90105
)
91106
topological_order = list()
92107

93108
while queue:
94-
service_name = queue.popleft()
95-
topological_order.append(service_name)
109+
service_node = queue.popleft()
110+
topological_order.append(service_node)
96111

97-
for dependency in self.graph[service_name]:
98-
in_degree[dependency] -= 1
99-
if in_degree[dependency] == 0:
100-
queue.append(dependency)
112+
for dependency_node in self.graph[service_node]:
113+
in_degree[dependency_node] -= 1
114+
if in_degree[dependency_node] == 0:
115+
queue.append(dependency_node)
101116

102117
if len(topological_order) != len(self.graph):
103118
# TODO: Add a better exception
104119
raise ValueError("Cycle detected in the dependency graph")
105120

106121
return topological_order
107122

108-
def get_starting_order(self) -> list[str]:
123+
def get_starting_order(self) -> list[DependencyNode]:
109124
return list(reversed(self.topological_sort()))
110125

111126

@@ -729,7 +744,18 @@ def _construct_dependency_graph(
729744
# Skip the dependency if it's not in the modes (since it may not be installed and we don't care about it)
730745
if dependency_name not in service_mode_dependencies:
731746
continue
732-
dependency_graph.add_edge(service_config.service_name, dependency_name)
747+
dependency_graph.add_edge(
748+
DependencyNode(
749+
name=service_config.service_name,
750+
dependency_type=DependencyType.SERVICE,
751+
),
752+
DependencyNode(
753+
name=dependency_name,
754+
dependency_type=DependencyType.SERVICE
755+
if _has_remote_config(dependency.remote)
756+
else DependencyType.COMPOSE,
757+
),
758+
)
733759
if _has_remote_config(dependency.remote):
734760
dependency_config = get_remote_dependency_config(dependency.remote)
735761
_construct_dependency_graph(dependency_config, [dependency.remote.mode])

0 commit comments

Comments
 (0)