Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Changelog
Unreleased
----------

* Added `find_matching_modules` and `find_matching_direct_imports` methods.
* Added `as_packages` option to the `find_shortest_chain` method.

3.6 (2025-02-07)
Expand Down
52 changes: 52 additions & 0 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ Methods for analysing the module tree
:raises: ``ValueError`` if the module is a squashed module, as by definition it represents both itself and all
of its descendants.

.. py:function:: ImportGraph.find_matching_modules(expression)

Find all modules matching the passed expression (see :ref:`module_expressions`).

:param str expression: A module expression used for matching.
:return: A set of module names matching the expression.
:rtype: A set of strings.
:raises: ``grimp.exceptions.InvalidModuleExpression`` if the module expression is invalid.


Methods for analysing direct imports
------------------------------------

Expand Down Expand Up @@ -184,6 +194,28 @@ Methods for analysing direct imports
So if a module is imported twice from the same module, it will only be counted once.
:rtype: Integer.

.. py:function:: ImportGraph.find_matching_direct_imports(import_expression)

Find all direct imports matching the passed import expression.

The imports are returned are in the following form::

[
{
'importer': 'mypackage.importer',
'imported': 'mypackage.imported',
},
# (additional imports here)
]

:param str import_expression: An expression in the form ``"importer_expression -> imported_expression"``,
where each expression is a module expression (see :ref:`module_expressions`).
Example: ``"mypackage.*.blue -> mypackage.*.green"``.
:return: An ordered list of direct imports matching the expressions (ordered alphabetically).
:rtype: List of dictionaries with the structure shown above. If you want to use type annotations, you may use the
``grimp.Import`` TypedDict for each dictionary.
:raises: ``grimp.exceptions.InvalidImportExpression`` if the expression is not well-formed.

Methods for analysing import chains
-----------------------------------

Expand Down Expand Up @@ -517,5 +549,25 @@ Methods for manipulating the graph
:param str module: The name of a module, for example ``'mypackage.foo'``.
:return: bool

.. _module_expressions:

Module expressions
------------------

A module expression is used to refer to sets of modules.

- ``*`` stands in for a module name, without including subpackages.
- ``**`` includes subpackages too.

Examples:

- ``mypackage.foo``: matches ``mypackage.foo`` exactly.
- ``mypackage.*``: matches ``mypackage.foo`` but not ``mypackage.foo.bar``.
- ``mypackage.*.baz``: matches ``mypackage.foo.baz`` but not ``mypackage.foo.bar.baz``.
- ``mypackage.*.*``: matches ``mypackage.foo.bar`` and ``mypackage.foobar.baz``.
- ``mypackage.**``: matches ``mypackage.foo.bar`` and ``mypackage.foo.bar.baz``.
- ``mypackage.**.qux``: matches ``mypackage.foo.bar.qux`` and ``mypackage.foo.bar.baz.qux``.
- ``mypackage.foo*``: is not a valid expression. (The wildcard must replace a whole module name.)

.. _namespace packages: https://docs.python.org/3/glossary.html#term-namespace-package
.. _namespace portion: https://docs.python.org/3/glossary.html#term-portion
110 changes: 107 additions & 3 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ itertools = "0.14.0"
tap = "1.0.1"
rustc-hash = "2.1.0"
indexmap = "2.7.1"
regex = "1.11.1"
const_format = "0.2.34"

[dependencies.pyo3]
version = "0.23.4"
Expand All @@ -29,4 +31,5 @@ extension-module = ["pyo3/extension-module"]
default = ["extension-module"]

[dev-dependencies]
parameterized = "2.0.0"
serde_json = "1.0.137"
8 changes: 7 additions & 1 deletion rust/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::exceptions::{ModuleNotPresent, NoSuchContainer};
use crate::exceptions::{InvalidModuleExpression, ModuleNotPresent, NoSuchContainer};
use pyo3::exceptions::PyValueError;
use pyo3::PyErr;
use thiserror::Error;
Expand All @@ -13,6 +13,9 @@ pub enum GrimpError {

#[error("Modules have shared descendants.")]
SharedDescendants,

#[error("{0} is not a valid module expression.")]
InvalidModuleExpression(String),
}

pub type GrimpResult<T> = Result<T, GrimpError>;
Expand All @@ -24,6 +27,9 @@ impl From<GrimpError> for PyErr {
GrimpError::ModuleNotPresent(_) => ModuleNotPresent::new_err(value.to_string()),
GrimpError::NoSuchContainer(_) => NoSuchContainer::new_err(value.to_string()),
GrimpError::SharedDescendants => PyValueError::new_err(value.to_string()),
GrimpError::InvalidModuleExpression(_) => {
InvalidModuleExpression::new_err(value.to_string())
}
}
}
}
5 changes: 5 additions & 0 deletions rust/src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ use pyo3::create_exception;

create_exception!(_rustgrimp, ModuleNotPresent, pyo3::exceptions::PyException);
create_exception!(_rustgrimp, NoSuchContainer, pyo3::exceptions::PyException);
create_exception!(
_rustgrimp,
InvalidModuleExpression,
pyo3::exceptions::PyException
);
Loading
Loading