Skip to content

Commit 4bc1872

Browse files
Fix case of empty package directory under usethis tool import-linter
1 parent 4832e31 commit 4bc1872

4 files changed

Lines changed: 28 additions & 1 deletion

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,8 @@ lint.select = [
136136
lint.ignore = [ "PLR2004", "S101", "SIM108" ]
137137
lint.per-file-ignores."!tests/**/*.py" = [ "ARG002", "PT" ]
138138
lint.per-file-ignores."src/usethis/_ui/interface/**/*.py" = [ "PLR0913" ]
139-
lint.per-file-ignores."tests/**/*.py" = [ "D", "INP", "S603", "TC" ]
140139

140+
lint.per-file-ignores."tests/**" = [ "D", "INP", "S603", "TC" ]
141141
lint.flake8-bugbear.extend-immutable-calls = [ "typer.Argument", "typer.Option" ]
142142
lint.flake8-builtins.strict-checking = true
143143
lint.flake8-tidy-imports.banned-api."typer.testing.CliRunner".msg = "Use `usethis._test.CliRunner` instead of `typer.CliRunner`."

src/usethis/_integrations/project/imports.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ def get_layered_architectures(pkg_name: str) -> dict[str, LayeredArchitecture]:
3434
3535
This is intended to inform the basis of a layer contract.
3636
37+
Note that the resulting dictionary may be empty if the package seems to be empty.
38+
3739
Reference:
3840
https://import-linter.readthedocs.io/en/stable/contract_types.html#layers
3941

src/usethis/_tool/impl/import_linter.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ def get_root_packages() -> list[str] | NoConfigValue:
242242
keys=["tool", "importlinter", "contracts"],
243243
get_value=lambda: contracts,
244244
),
245+
# N.B. these INI sections are added via
246+
# `ini_contracts_config_items`
247+
# but there might be others so we still need to declare they
248+
# are associated with this tool based on regex.
245249
Path(".importlinter"): ConfigEntry(
246250
keys=[re.compile("importlinter:contract:.*")]
247251
),
@@ -271,6 +275,9 @@ def _get_layered_architecture_by_module_by_root_package(
271275
try:
272276
layered_architecture_by_module = get_layered_architectures(root_package)
273277
except ImportGraphBuildFailedError:
278+
layered_architecture_by_module = {}
279+
280+
if not layered_architecture_by_module:
274281
layered_architecture_by_module = {
275282
root_package: LayeredArchitecture(layers=[], excluded=set())
276283
}

tests/usethis/_tool/impl/test_import_linter.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,24 @@ def test_pyproject_toml_exists(self, tmp_path: Path):
156156
assert not (tmp_path / ".importlinter").exists()
157157
assert (tmp_path / "pyproject.toml").exists()
158158

159+
class TestGetConfigSpec:
160+
def test_empty_src_directory(self, tmp_path: Path):
161+
# Arrange: Create empty src directory with package subdirectory
162+
# src/ contains mypkg/ but mypkg/ is completely empty (no __init__.py)
163+
(tmp_path / "pyproject.toml").write_text('[project]\nname = "mypkg"')
164+
(tmp_path / "src").mkdir()
165+
(tmp_path / "src" / "mypkg").mkdir()
166+
# mypkg directory is empty - no __init__.py, no files
167+
168+
# Act: get_config_spec should not crash with AssertionError
169+
# when get_importable_packages returns empty and grimp fails
170+
with change_cwd(tmp_path), files_manager():
171+
config_spec = ImportLinterTool().get_config_spec()
172+
173+
# Assert: Should return a valid config spec with fallback contract
174+
assert config_spec is not None
175+
assert len(config_spec.config_items) > 0
176+
159177

160178
class TestIsINPRule:
161179
def test_inp_rule(self):

0 commit comments

Comments
 (0)