Skip to content
Draft
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
6 changes: 4 additions & 2 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ Methods for analysing direct imports

This method should not be used to determine whether an import is present:
some of the imports in the graph may have no available metadata. For example, if an import
has been added by the ``add_import`` method without the ``line_number`` and ``line_contents`` specified, then
has been added by the ``add_import`` method without the optional arguments, then
calling this method on the import will return an empty list. If you want to know whether the import is present,
use ``direct_import_exists``.

Expand All @@ -174,6 +174,7 @@ Methods for analysing direct imports
{
'importer': 'mypackage.importer',
'imported': 'mypackage.imported',
'is_lazy': False,
'line_number': 5,
'line_contents': 'from mypackage import imported',
},
Expand Down Expand Up @@ -560,13 +561,14 @@ Methods for manipulating the graph
:param str module: The name of a module, for example ``'mypackage.foo'``.
:return: None

.. py:function:: ImportGraph.add_import(importer, imported, line_number=None, line_contents=None)
.. py:function:: ImportGraph.add_import(importer, imported, is_lazy=False, line_number=None, line_contents=None)

Add a direct import between two modules to the graph. If the modules are not already
present, they will be added to the graph.

:param str importer: The name of the module that is importing the other module.
:param str imported: The name of the module being imported.
:param bool is_lazy: Whether the import is an explicit lazy import.
:param int line_number: The line number of the import statement in the module.
:param str line_contents: The line that contains the import statement.
:return: None
Expand Down
1 change: 1 addition & 0 deletions rust/src/caching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ pub fn parse_json_to_map(
imported,
line_number,
line_contents,
is_lazy: false, // TODO get working with cache.
})
.collect();
parsed_map.insert(module, import_set);
Expand Down
3 changes: 2 additions & 1 deletion rust/src/graph/graph_manipulation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ impl Graph {
imported: ModuleToken,
line_number: u32,
line_contents: &str,
is_lazy: bool,
) {
self.imports
.entry(importer)
Expand All @@ -137,7 +138,7 @@ impl Graph {
self.import_details
.entry((importer, imported))
.or_default()
.insert(PyImportDetails::new(line_number, line_contents));
.insert(PyImportDetails::new(line_number, line_contents, is_lazy));
}
}

Expand Down
26 changes: 18 additions & 8 deletions rust/src/graph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,27 +264,31 @@ impl GraphWrapper {
Ok(self.get_visible_module_by_name(module)?.is_squashed())
}

#[pyo3(signature = (*, importer, imported, line_number=None, line_contents=None))]
#[pyo3(signature = (*, importer, imported, is_lazy=None, line_number=None, line_contents=None))]
pub fn add_import(
&mut self,
importer: &str,
imported: &str,
is_lazy: Option<bool>,
line_number: Option<u32>,
line_contents: Option<&str>,
) {
let importer = self._graph.get_or_add_module(importer).token();
let imported = self._graph.get_or_add_module(imported).token();
match (line_number, line_contents) {
(Some(line_number), Some(line_contents)) => {
self._graph
.add_detailed_import(importer, imported, line_number, line_contents)
}
(None, None) => {
match (is_lazy, line_number, line_contents) {
(Some(is_lazy), Some(line_number), Some(line_contents)) => self._graph.add_detailed_import(
importer,
imported,
line_number,
line_contents,
is_lazy,
),
(None, None, None) => {
self._graph.add_import(importer, imported);
}
_ => {
// TODO handle better.
panic!("Expected line_number and line_contents, or neither.");
panic!("If any of is_lazy, line_number and line_contents are provided, they all must be provided.");
}
}
}
Expand Down Expand Up @@ -402,6 +406,7 @@ impl GraphWrapper {
imported.name(),
import_details.line_number(),
import_details.line_contents(),
import_details.is_lazy(),
)
})
.sorted()
Expand All @@ -417,6 +422,7 @@ impl GraphWrapper {
"line_contents",
import_details.line_contents.into_py_any(py).unwrap(),
),
("is_lazy", import_details.is_lazy.into_py_any(py).unwrap()),
]
.into_py_dict(py)
.unwrap()
Expand Down Expand Up @@ -667,6 +673,7 @@ struct ImportDetails {
imported: String,
line_number: u32,
line_contents: String,
is_lazy: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, new)]
Expand Down Expand Up @@ -754,6 +761,9 @@ pub struct PyImportDetails {

#[getset(get_copy = "pub")]
interned_line_contents: DefaultSymbol,

#[getset(get_copy = "pub")]
is_lazy: bool,
}

impl PyImportDetails {
Expand Down
5 changes: 5 additions & 0 deletions rust/src/import_parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub struct ImportedObject {
pub line_number: usize,
pub line_contents: String,
pub typechecking_only: bool,
pub is_lazy: bool,
}

impl ImportedObject {
Expand All @@ -18,12 +19,14 @@ impl ImportedObject {
line_number: usize,
line_contents: String,
typechecking_only: bool,
is_lazy: bool,
) -> Self {
Self {
name,
line_number,
line_contents,
typechecking_only,
is_lazy,
}
}
}
Expand Down Expand Up @@ -84,6 +87,7 @@ impl<'a> StatementVisitor<'a> for Visitor<'a> {
line_number.get(),
self.source_code.line_text(line_number).trim().to_string(),
self.typechecking_only,
import_stmt.is_lazy,
))
}
walk_stmt(self, stmt);
Expand Down Expand Up @@ -113,6 +117,7 @@ impl<'a> StatementVisitor<'a> for Visitor<'a> {
line_number.get(),
self.source_code.line_text(line_number).trim().to_string(),
self.typechecking_only,
import_from_stmt.is_lazy,
))
}
walk_stmt(self, stmt);
Expand Down
6 changes: 6 additions & 0 deletions rust/src/import_scanning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub struct DirectImport {
pub imported: String,
pub line_number: usize,
pub line_contents: String,
pub is_lazy: bool,
}

impl<'a, 'py> FromPyObject<'a, 'py> for DirectImport {
Expand All @@ -26,12 +27,14 @@ impl<'a, 'py> FromPyObject<'a, 'py> for DirectImport {
let imported: String = ob.getattr("imported")?.getattr("name")?.extract()?;
let line_number: usize = ob.getattr("line_number")?.extract()?;
let line_contents: String = ob.getattr("line_contents")?.extract()?;
let is_lazy: bool = ob.getattr("is_lazy")?.extract()?;

Ok(DirectImport {
importer,
imported,
line_number,
line_contents,
is_lazy,
})
}
}
Expand Down Expand Up @@ -152,6 +155,7 @@ fn scan_for_imports_no_py_single_module(
imported: imported_module.name.to_string(),
line_number: imported_object.line_number,
line_contents: imported_object.line_contents,
is_lazy: imported_object.is_lazy,
});
}
None => {
Expand All @@ -165,6 +169,7 @@ fn scan_for_imports_no_py_single_module(
imported: imported_module,
line_number: imported_object.line_number,
line_contents: imported_object.line_contents,
is_lazy: imported_object.is_lazy,
});
}
}
Expand Down Expand Up @@ -196,6 +201,7 @@ fn to_py_direct_imports<'a>(
kwargs
.set_item("line_contents", &rust_import.line_contents)
.unwrap();
kwargs.set_item("is_lazy", rust_import.is_lazy).unwrap();
let py_direct_import = py_direct_import_class.call((), Some(&kwargs)).unwrap();
pyset.add(&py_direct_import).unwrap();
}
Expand Down
4 changes: 4 additions & 0 deletions src/grimp/application/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class Import(TypedDict):
class DetailedImport(Import):
line_number: int
line_contents: str
is_lazy: bool


class ImportGraph:
Expand Down Expand Up @@ -138,6 +139,7 @@ def add_import(
imported: str,
line_number: int | None = None,
line_contents: str | None = None,
is_lazy: bool | None = None,
) -> None:
"""
Add a direct import between two modules to the graph. If the modules are not already
Expand All @@ -149,6 +151,7 @@ def add_import(
imported=imported,
line_number=line_number,
line_contents=line_contents,
is_lazy=is_lazy,
)

def remove_import(self, *, importer: str, imported: str) -> None:
Expand Down Expand Up @@ -249,6 +252,7 @@ def get_import_details(self, *, importer: str, imported: str) -> list[DetailedIm
'imported': 'mypackage.imported',
'line_number': 5,
'line_contents': 'from mypackage import imported',
'is_lazy': False,
},
(additional imports here)
]
Expand Down
1 change: 1 addition & 0 deletions src/grimp/application/usecases.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def _assemble_graph(
imported=direct_import.imported.name,
line_number=direct_import.line_number,
line_contents=direct_import.line_contents,
is_lazy=direct_import.is_lazy,
)
return graph

Expand Down
5 changes: 4 additions & 1 deletion src/grimp/domain/valueobjects.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ class DirectImport:
imported: Module
line_number: int
line_contents: str
# Import in the form `lazy import` or `lazy from import`.
is_lazy: bool = False

def __str__(self) -> str:
return f"{self.importer} -> {self.imported} (l. {self.line_number})"
lazy_label = ", lazy" if self.is_lazy else ""
return f"{self.importer} -> {self.imported} (l. {self.line_number}{lazy_label})"


@dataclass(frozen=True, order=True)
Expand Down
1 change: 1 addition & 0 deletions tests/functional/test_build_and_use_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ def test_get_import_details():
{
"importer": "testpackage.utils",
"imported": "testpackage.two.alpha",
"is_lazy": False,
"line_number": 5,
"line_contents": "from .two import alpha",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def test_stores_import_within_package(self, root_packages):
{
"importer": "rootpackageblue.two",
"imported": "rootpackageblue.one.alpha",
"is_lazy": False,
"line_number": 1,
"line_contents": "from .one.alpha import BAR",
}
Expand All @@ -60,6 +61,7 @@ def test_stores_import_between_root_packages(self, root_packages):
{
"importer": "rootpackagegreen.two",
"imported": "rootpackageblue.one.alpha",
"is_lazy": False,
"line_number": 1,
"line_contents": "from rootpackageblue.one import alpha",
}
Expand Down
2 changes: 2 additions & 0 deletions tests/functional/test_caching.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def test_build_graph_uses_cache(copied_cachingpackage):
{
"importer": "cachingpackage.two.alpha",
"imported": "cachingpackage.one.alpha",
"is_lazy": False,
"line_contents": "from ..one import alpha",
"line_number": 1,
},
Expand Down Expand Up @@ -80,6 +81,7 @@ def test_build_graph_uses_cache(copied_cachingpackage):
{
"importer": "cachingpackage.two.alpha",
"imported": "cachingpackage.one.alpha",
"is_lazy": False,
"line_contents": replacement,
"line_number": 1,
},
Expand Down
2 changes: 2 additions & 0 deletions tests/functional/test_encoding_handling.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def test_build_graph_of_non_ascii_source():
{
"importer": "encodingpackage.importer",
"imported": "encodingpackage.imported",
"is_lazy": False,
"line_number": 1,
"line_contents": "from .imported import π",
},
Expand All @@ -35,6 +36,7 @@ def test_build_graph_of_non_utf8_source():
{
"importer": "encodingpackage.shift_jis_importer",
"imported": "encodingpackage.imported",
"is_lazy": False,
"line_number": 3,
"line_contents": "from .imported import π",
},
Expand Down
10 changes: 10 additions & 0 deletions tests/functional/test_lazy_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,13 @@ def test_build_graph_with_lazy_imports():
"lazyimports.two.blue",
"lazyimports.two.green",
} == result
# Spot check that one is stored as is_lazy.
assert [
{
"importer": "lazyimports.one",
"imported": "lazyimports.two",
"is_lazy": True,
"line_number": 2,
"line_contents": "lazy from lazyimports import two",
}
] == graph.get_import_details(importer="lazyimports.one", imported="lazyimports.two")
1 change: 1 addition & 0 deletions tests/unit/application/graph/test_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ def test_doesnt_lose_import_details(self, as_packages: bool):
{
"importer": "green.foo",
"imported": "blue.bar",
"is_lazy": False,
"line_contents": "import blue.bar",
"line_number": 5,
}
Expand Down
1 change: 1 addition & 0 deletions tests/unit/application/graph/test_copy.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def test_removing_import_doesnt_affect_copy(self):
{
"importer": "foo",
"imported": "bar",
"is_lazy": False,
"line_number": 3,
"line_contents": "import bar",
}
Expand Down
3 changes: 3 additions & 0 deletions tests/unit/application/graph/test_direct_imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,14 @@ def test_happy_path(self):
dict(
importer="mypackage.foo",
imported="mypackage.bar",
is_lazy=False,
line_number=1,
line_contents="from . import bar",
),
dict(
importer="mypackage.foo",
imported="mypackage.bar",
is_lazy=True,
line_number=10,
line_contents="from .bar import a_function",
),
Expand Down Expand Up @@ -267,6 +269,7 @@ def test_returns_only_relevant_imports(self):
dict(
importer="mypackage.foo",
imported="mypackage.bar",
is_lazy=False,
line_number=1,
line_contents="from . import bar",
)
Expand Down
Loading
Loading