Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,5 @@ dmypy.json

# Pyre type checker
.pyre/

.idea/
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
# dag-dependency-resolver
# dag-dependency-resolver

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
[![Static typing: mypy](https://img.shields.io/badge/%20static-mypy-%3167400?style=flat&labelColor=A09711)](http://mypy-lang.org/)
Empty file added extras/__init__.py
Empty file.
Empty file added extras/yaml/__init__.py
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[tool.black]
line-length = 120
target-version = ['py38']
Empty file added requirements.txt
Empty file.
6 changes: 6 additions & 0 deletions requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pytest==7.1.2
unittest
black==22.3.0
isort==5.10.1
flake8==5.0.4
mypy==0.990
45 changes: 45 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[tool:isort]
line_length=120
multi_line_output=3
force_grid_wrap=True
indent=4
use_parentheses=True
include_trailing_comma=True
lines_after_imports=2
combine_as_imports=True
skip=venv

[flake8]
ignore = C901, W503
exclude =
.git,
venv,
.venv,
./venv,
__pycache__

max-line-length = 120
max-complexity = 10
show-source = True
statistics = True


[mypy]
python_version = 3.8
show_error_codes = True
show_error_context = True
pretty = True
warn_unused_configs = True
warn_unused_ignores = True
ignore_missing_imports = True
warn_unreachable = True
warn_return_any = True
warn_redundant_casts = True
no_implicit_optional = True
disallow_untyped_decorators = True
check_untyped_defs = True
disallow_incomplete_defs = True
disallow_untyped_defs = True
disallow_untyped_calls = True
disallow_subclassing_any = False
disallow_any_generics = True
Empty file added setup.py
Empty file.
Empty file added src/__init__.py
Empty file.
Empty file added src/enums/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions src/enums/node_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from enum import (
Enum,
auto,
)


class NodeStatus(Enum):
VISITED = auto()
UNVISITED = auto()
42 changes: 42 additions & 0 deletions src/graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from dataclasses import (
dataclass,
field,
)
from typing import (
Dict,
List,
Text,
)

from src.enums.node_status import NodeStatus
from src.node import Node


@dataclass
class DependencyGraph:
nodes: Dict[Text, Node] = field(default_factory=dict)

def add_node(self, name: str, parents: List[Text]) -> None:
self.nodes[name] = Node(name=name, parents=parents)

def get_dependencies_order(self) -> List[str]:
dependencies_order: List[str] = []

for node in self.nodes.values():
self._solve_deps(node, dependencies_order)

self._reset_nodes_status()
return dependencies_order

def _solve_deps(self, node: Node, dependencies_order: List[Text]) -> None:
if node.status != NodeStatus.VISITED:
for parent in node.parents:
self._solve_deps(self.nodes[parent], dependencies_order)

node.status = NodeStatus.VISITED

dependencies_order.append(node.name)

def _reset_nodes_status(self) -> None:
for node in self.nodes.values():
node.status = NodeStatus.UNVISITED
17 changes: 17 additions & 0 deletions src/node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from dataclasses import (
dataclass,
field,
)
from typing import (
List,
Text,
)

from src.enums.node_status import NodeStatus


@dataclass
class Node:
name: Text
parents: List[Text] = field(default_factory=list)
status: NodeStatus = field(default=NodeStatus.UNVISITED)
Empty file added tests/__init__.py
Empty file.
32 changes: 32 additions & 0 deletions tests/test_graph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import unittest

from src.graph import DependencyGraph
from src.node import Node


class TestStringMethods(unittest.TestCase):
def setUp(self) -> None:
self.dependency_graph = DependencyGraph()

def test_add_node(self) -> None:
self.dependency_graph.add_node(name="c", parents=["a", "b"])

self.assertDictEqual(self.dependency_graph.nodes, {"c": Node(name="c", parents=["a", "b"])})

def test_get_dependencies_order(self) -> None:
self.dependency_graph.add_node(name="a", parents=["b", "c"])
self.dependency_graph.add_node(name="b", parents=[])
self.dependency_graph.add_node(name="c", parents=[])

dependencies_order = self.dependency_graph.get_dependencies_order()

self.assertListEqual(dependencies_order, ["b", "c", "a"])

def test_get_dependencies_order_disconnected_graph(self) -> None:
self.dependency_graph.add_node(name="a", parents=[])
self.dependency_graph.add_node(name="b", parents=[])
self.dependency_graph.add_node(name="c", parents=[])

dependencies_order = self.dependency_graph.get_dependencies_order()

self.assertListEqual(dependencies_order, ["a", "b", "c"])