Created
February 18, 2025 05:00
-
-
Save layus/bbb1a4c699f3d48ff18f08ec76486558 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/fawltydeps/check.py b/fawltydeps/check.py | |
index f48139d..8c67173 100644 | |
--- a/fawltydeps/check.py | |
+++ b/fawltydeps/check.py | |
@@ -2,9 +2,13 @@ | |
import logging | |
from itertools import groupby | |
-from typing import Dict, List | |
+from typing import Dict, List, Iterable | |
-from fawltydeps.packages import Package | |
+from fawltydeps.packages import ( | |
+ BasePackageResolver, | |
+ Package, | |
+ resolve_dependencies, | |
+) | |
from fawltydeps.settings import Settings | |
from fawltydeps.types import ( | |
DeclaredDependency, | |
@@ -15,10 +19,16 @@ from fawltydeps.types import ( | |
logger = logging.getLogger(__name__) | |
+def _candidates(import_name: str, known_packages: Iterable[BasePackageResolver]) -> List[Package]: | |
+ for resolver in known_packages: | |
+ for package in resolver.all_packages(): | |
+ if import_name in package.import_names: | |
+ yield package | |
def calculate_undeclared( | |
imports: List[ParsedImport], | |
resolved_deps: Dict[str, Package], | |
+ known_packages: Iterable[BasePackageResolver], | |
settings: Settings, | |
) -> List[UndeclaredDependency]: | |
"""Calculate which imports are not covered by declared dependencies. | |
@@ -35,7 +45,7 @@ def calculate_undeclared( | |
] | |
undeclared.sort(key=lambda i: i.name) # groupby requires pre-sorting | |
return [ | |
- UndeclaredDependency(name, [i.source for i in imports]) | |
+ UndeclaredDependency(name, [i.source for i in imports], list(_candidates(name, known_packages))) | |
for name, imports in groupby(undeclared, key=lambda i: i.name) | |
] | |
diff --git a/fawltydeps/cli_parser.py b/fawltydeps/cli_parser.py | |
index 5cf2f10..8441aa0 100644 | |
--- a/fawltydeps/cli_parser.py | |
+++ b/fawltydeps/cli_parser.py | |
@@ -223,6 +223,11 @@ def populate_parser_paths_options(parser: argparse._ActionsContainer) -> None: | |
" defined by the user." | |
), | |
) | |
+ parser.add_argument( | |
+ "--base_path", | |
+ type=Path, | |
+ metavar="BASE_PATH", | |
+ ) | |
def populate_parser_configuration(parser: argparse._ActionsContainer) -> None: | |
diff --git a/fawltydeps/dir_traversal.py b/fawltydeps/dir_traversal.py | |
index 1a8ffe8..77e3e88 100644 | |
--- a/fawltydeps/dir_traversal.py | |
+++ b/fawltydeps/dir_traversal.py | |
@@ -129,7 +129,9 @@ class DirectoryTraversal(Generic[T]): | |
instance, it will _not_ be re-traversed. | |
""" | |
if not dir_path.is_dir(): | |
- raise NotADirectoryError(dir_path) | |
+ #raise NotADirectoryError(dir_path) | |
+ return | |
+ dir_path = Path() | |
dir_id = DirId.from_path(dir_path) | |
self.to_traverse[dir_path] = dir_id | |
self.attached.setdefault(dir_id, []).extend(attach_data) | |
diff --git a/fawltydeps/main.py b/fawltydeps/main.py | |
index fb467d6..99cf21f 100644 | |
--- a/fawltydeps/main.py | |
+++ b/fawltydeps/main.py | |
@@ -135,27 +135,32 @@ class Analysis: | |
) | |
) | |
+ @property | |
+ @calculated_once | |
+ def resolvers(self) -> Iterable[BasePackageResolver]: | |
+ pyenv_srcs = {src for src in self.sources if isinstance(src, PyEnvSource)} | |
+ return list(setup_resolvers( | |
+ custom_mapping_files=self.settings.custom_mapping_file, | |
+ custom_mapping=self.settings.custom_mapping, | |
+ pyenv_srcs=pyenv_srcs, | |
+ use_current_env=True, | |
+ install_deps=self.settings.install_deps, | |
+ )) | |
+ | |
@property | |
@calculated_once | |
def resolved_deps(self) -> Dict[str, Package]: | |
"""The resolved mapping of dependency names to provided import names.""" | |
- pyenv_srcs = {src for src in self.sources if isinstance(src, PyEnvSource)} | |
return resolve_dependencies( | |
(dep.name for dep in self.declared_deps), | |
- setup_resolvers( | |
- custom_mapping_files=self.settings.custom_mapping_file, | |
- custom_mapping=self.settings.custom_mapping, | |
- pyenv_srcs=pyenv_srcs, | |
- use_current_env=True, | |
- install_deps=self.settings.install_deps, | |
- ), | |
+ self.resolvers, | |
) | |
@property | |
@calculated_once | |
def undeclared_deps(self) -> List[UndeclaredDependency]: | |
"""The import statements for which no declared dependency is found.""" | |
- return calculate_undeclared(self.imports, self.resolved_deps, self.settings) | |
+ return calculate_undeclared(self.imports, self.resolved_deps, self.resolvers, self.settings) | |
@property | |
@calculated_once | |
diff --git a/fawltydeps/packages.py b/fawltydeps/packages.py | |
index efb0e72..26b7605 100644 | |
--- a/fawltydeps/packages.py | |
+++ b/fawltydeps/packages.py | |
@@ -116,6 +116,9 @@ class BasePackageResolver(ABC): | |
""" | |
raise NotImplementedError | |
+ def all_packages(self) -> Iterable[Package]: | |
+ return [] | |
+ | |
def accumulate_mappings( | |
resolved_with: Type[BasePackageResolver], | |
@@ -205,6 +208,8 @@ class UserDefinedMapping(BasePackageResolver): | |
if Package.normalize_name(name) in self.packages | |
} | |
+ def all_packages(self) -> Iterable[Package]: | |
+ return self.packages.values() | |
class InstalledPackageResolver(BasePackageResolver): | |
"""Lookup imports exposed by packages installed in a Python environment.""" | |
@@ -282,6 +287,9 @@ class InstalledPackageResolver(BasePackageResolver): | |
if Package.normalize_name(name) in self.packages | |
} | |
+ def all_packages(self) -> Iterable[Package]: | |
+ return self.packages.values() | |
+ | |
class SysPathPackageResolver(InstalledPackageResolver): | |
"""Lookup imports exposed by packages installed in sys.path.""" | |
@@ -639,7 +647,9 @@ def validate_pyenv_source(path: Path) -> Optional[Set[PyEnvSource]]: | |
- Raise UnparseablePathError if the given path is not a directory. | |
""" | |
if not path.is_dir(): | |
- raise UnparseablePathError(ctx="Not a directory!", path=path) | |
+ #raise UnparseablePathError(ctx="Not a directory!", path=path) | |
+ # maybe just a warning ? | |
+ return None | |
try: | |
return pyenv_sources(path) | |
except ValueError: | |
diff --git a/fawltydeps/settings.py b/fawltydeps/settings.py | |
index dbf1467..8327ac8 100644 | |
--- a/fawltydeps/settings.py | |
+++ b/fawltydeps/settings.py | |
@@ -156,6 +156,7 @@ class Settings(BaseSettings): | |
""" | |
actions: Set[Action] = {Action.REPORT_UNDECLARED, Action.REPORT_UNUSED} | |
+ base_path: Optional[Path] = None | |
output_format: OutputFormat = OutputFormat.HUMAN_SUMMARY | |
code: Set[PathOrSpecial] = {Path()} | |
deps: Set[Path] = {Path()} | |
diff --git a/fawltydeps/traverse_project.py b/fawltydeps/traverse_project.py | |
index e48406d..90fb558 100644 | |
--- a/fawltydeps/traverse_project.py | |
+++ b/fawltydeps/traverse_project.py | |
@@ -102,7 +102,7 @@ def find_sources( # noqa: C901, PLR0912, PLR0915 | |
for path_or_special in settings.code if CodeSource in source_types else []: | |
# exceptions raised by validate_code_source() are propagated here | |
- validated: Optional[Source] = validate_code_source(path_or_special) | |
+ validated: Optional[Source] = validate_code_source(path_or_special, settings.base_path) | |
if validated is not None: # parse-able file given directly | |
logger.debug(f"find_sources() Found {validated}") | |
yield validated | |
diff --git a/fawltydeps/types.py b/fawltydeps/types.py | |
index bd3d909..f429826 100644 | |
--- a/fawltydeps/types.py | |
+++ b/fawltydeps/types.py | |
@@ -286,6 +286,7 @@ class UndeclaredDependency: | |
name: str | |
references: List[Location] | |
+ candidates: List[Package] | |
def render(self, *, include_references: bool) -> str: | |
"""Return a human-readable string representation. | |
diff --git a/fawltydeps/utils.py b/fawltydeps/utils.py | |
index 4639247..82ec15f 100644 | |
--- a/fawltydeps/utils.py | |
+++ b/fawltydeps/utils.py | |
@@ -27,9 +27,10 @@ def version() -> str: | |
def dirs_between(parent: Path, child: Path) -> Iterator[Path]: | |
"""Yield directories between 'parent' and 'child', inclusive.""" | |
- yield child | |
- if child != parent: | |
- yield from dirs_between(parent, child.parent) | |
+ return [ p for p in child.parents if str(p).startswith(str(parent))] or [parent] | |
+ #yield child | |
+ #if child != parent: | |
+ # yield from dirs_between(parent, child.parent) | |
def hide_dataclass_fields(instance: object, *field_names: str) -> None: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment