Source code for score.init.dependency
# vim: set fileencoding=UTF-8
# Copyright © 2015-2018 STRG.AT GmbH, Vienna, Austria
#
# This file is part of the The SCORE Framework.
#
# The SCORE Framework and all its parts are free software: you can redistribute
# them and/or modify them under the terms of the GNU Lesser General Public
# License version 3 as published by the Free Software Foundation which is in the
# file named COPYING.LESSER.txt.
#
# The SCORE Framework and all its parts are distributed without any WARRANTY;
# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
# PARTICULAR PURPOSE. For more details see the GNU Lesser General Public
# License.
#
# If you have not received a copy of the GNU Lesser General Public License see
# http://www.gnu.org/licenses/.
#
# The License-Agreement realised between you as Licensee and STRG.AT GmbH as
# Licenser including the issue of its valid conclusion and its pre- and
# post-contractual effects is governed by the laws of Austria. Any disputes
# concerning this License-Agreement including the issue of its valid conclusion
# and its pre- and post-contractual effects are exclusively decided by the
# competent court, in whose district STRG.AT GmbH has its registered seat, at
# the discretion of STRG.AT GmbH also the competent court, in whose district the
# Licensee has his registered seat, an establishment or assets.
from .exceptions import DependencyLoop
[docs]class DependencySolver:
"""
A simple helper for resolving module interdependencies. Basic usage:
.. code-block:: python
solver = DependencySolver()
solver.add_dependency('a', 'b') # a depends on b
solver.add_dependency('b', 'c') # b depends on c
solver.solve() # returns all modules in the order in which they
# should be initialized: ['c', 'b', 'a']
"""
def __init__(self):
self._dependencies = dict()
def add_dependency(self, from_, to=None):
"""
Add dependency from module *from_* to module *to*. If a module has no
dependencies, you can still register it without a *to* argument to
ensure that it is included in the result set of the solve() call.
"""
if from_ not in self._dependencies:
self._dependencies[from_] = set()
if to is not None:
self._dependencies[from_].add(to)
add = add_dependency
def remove_dependency(self, from_, to):
"""
Removes a direct dependency. Does nothing, if there was no such
dependency.
"""
if from_ not in self._dependencies:
return
self._dependencies[from_].difference_update({to})
def direct_dependencies(self, node):
"""
Provides the direct dependencies of a given node.
"""
return list(self.direct_dependencies_iter(node))
def direct_dependencies_iter(self, node):
"""
Same as :meth:`direct_dependencies`, but returns an iterator.
"""
if node not in self._dependencies:
return
yield from self._dependencies[node]
def direct_dependents(self, node):
"""
Provides all modules that directly depend on this one.
"""
return list(self.direct_dependents_iter(node))
def direct_dependents_iter(self, node, *, __visited=None):
"""
Same as :meth:`direct_dependents`, but returns an iterator.
"""
if node not in self._dependencies:
return
for other, other_deps in self._dependencies.items():
if other == node or node not in other_deps:
continue
yield other
def has_direct_dependency(self, from_, to):
"""
Tests, if given node *from_* has a direct dependency to node *to*.
"""
return from_ in self._dependencies and to in self._dependencies[from_]
def solve(self):
"""
Solves the dependency system.
"""
sorted_ = []
dependencies = dict()
for item, item_dependencies in self._dependencies.items():
if not item_dependencies:
sorted_.append(item)
else:
dependencies[item] = item_dependencies
for dep in item_dependencies:
if dep not in self._dependencies:
sorted_.append(dep)
updated = True
while updated:
updated = False
for item in list(dependencies.keys()):
deps = dependencies[item]
to_remove = deps.intersection(sorted_)
if not to_remove:
continue
updated = True
if to_remove == deps:
sorted_.append(item)
del dependencies[item]
else:
deps.difference_update(sorted_)
if dependencies:
item = next(iter(dependencies.keys()))
loop = [item, dependencies[item].pop()]
while loop[-1] not in loop[:-1]:
loop.append(dependencies[loop[-1]].pop())
raise DependencyLoop(loop)
return sorted_