Skip to content

Commit

Permalink
Add as_packages parameter to find_shortest_chain
Browse files Browse the repository at this point in the history
The implementation of the pathfinding already supports
this, so let's add this to the public API.
  • Loading branch information
Peter554 committed Feb 8, 2025
1 parent 6076f49 commit ee16f7c
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Changelog
=========

Unreleased
----------

* Added `as_packages` option to the `find_shortest_chain` method.

3.6 (2025-02-07)
----------------

Expand Down
4 changes: 4 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ Methods for analysing import chains
:param str importer: The module at the start of a potential chain of imports between ``importer`` and ``imported``
(i.e. the module that potentially imports ``imported``, even indirectly).
:param str imported: The module at the end of the potential chain of imports.
:param bool as_packages: Whether to treat the supplied modules as individual modules,
or as packages (including any descendants, if there are any). If
treating them as packages, all descendants of ``importer`` and
``imported`` will be checked too.
:return: The shortest chain of imports between the supplied modules, or None if no chain exists.
:rtype: A tuple of strings, ordered from importer to imported modules, or None.

Expand Down
5 changes: 3 additions & 2 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,17 +296,18 @@ impl GraphWrapper {
.collect())
}

// TODO(peter) Add `as_packages` argument here? The implementation already supports it!
#[pyo3(signature = (importer, imported, as_packages=false))]
pub fn find_shortest_chain(
&self,
importer: &str,
imported: &str,
as_packages: bool,
) -> PyResult<Option<Vec<String>>> {
let importer = self.get_visible_module_by_name(importer)?.token();
let imported = self.get_visible_module_by_name(imported)?.token();
Ok(self
._graph
.find_shortest_chain(importer, imported, false)?
.find_shortest_chain(importer, imported, as_packages)?
.map(|chain| {
chain
.iter()
Expand Down
6 changes: 4 additions & 2 deletions src/grimp/adaptors/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,14 @@ def find_downstream_modules(self, module: str, as_package: bool = False) -> Set[
def find_upstream_modules(self, module: str, as_package: bool = False) -> Set[str]:
return self._rustgraph.find_upstream_modules(module, as_package)

def find_shortest_chain(self, importer: str, imported: str) -> tuple[str, ...] | None:
def find_shortest_chain(
self, importer: str, imported: str, as_packages: bool = False
) -> tuple[str, ...] | None:
for module in (importer, imported):
if not self._rustgraph.contains_module(module):
raise ValueError(f"Module {module} is not present in the graph.")

chain = self._rustgraph.find_shortest_chain(importer, imported)
chain = self._rustgraph.find_shortest_chain(importer, imported, as_packages)
return tuple(chain) if chain else None

def find_shortest_chains(
Expand Down
10 changes: 9 additions & 1 deletion src/grimp/application/ports/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,19 @@ def find_upstream_modules(self, module: str, as_package: bool = False) -> Set[st
raise NotImplementedError

@abc.abstractmethod
def find_shortest_chain(self, importer: str, imported: str) -> tuple[str, ...] | None:
def find_shortest_chain(
self, importer: str, imported: str, as_packages: bool = False
) -> tuple[str, ...] | None:
"""
Attempt to find the shortest chain of imports between two modules, in the direction
of importer to imported.
Optional args:
as_packages: Whether to treat the supplied modules as individual modules,
or as packages (including any descendants, if there are any). If
treating them as subpackages, all descendants of the supplied modules
will be checked too.
Returns:
Tuple of module names, from importer to imported, or None if no chain exists.
"""
Expand Down
19 changes: 19 additions & 0 deletions tests/unit/adaptors/graph/test_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ def test_demonstrate_nondeterminism_of_equal_chains(self):
other_chain = (source, d, e, f, destination)
assert (result == one_chain) or (result == other_chain)

@pytest.mark.parametrize(
"as_packages, expected_result",
(
(False, None),
(True, ("green.foo", "blue.bar")),
),
)
def test_as_packages(self, as_packages: bool, expected_result: Set[Tuple]):
graph = ImportGraph()
graph.add_module("green")
graph.add_module("blue")
graph.add_import(importer="green.foo", imported="blue.bar")

result = graph.find_shortest_chain(
importer="green", imported="blue", as_packages=as_packages
)

assert result == expected_result


class TestFindShortestChains:
@pytest.mark.parametrize(
Expand Down

0 comments on commit ee16f7c

Please # to comment.