Repository: DavidVujic/python-polylith

Issue:
poly libs: fails detecting library names with alias that differ
**Describe the bug**
A clear and concise description of what the bug is.
Example:
the library `python-youtube` is imported as `pyyoutube`. When running `polyl libs`, the library is reported as missing.


Possible solution: using difflib.

Example:
```bash
In [1]: import difflib

In [2]: difflib.get_close_matches("pyyoutube", ["requests", "python-youtube", "starlette", "fastapi"])
Out[2]: ['python-youtube']
```


Discussion:

Patch:
diff --git a/components/polylith/libs/report.py b/components/polylith/libs/report.py
index 03d59d5..6b75bd6 100644
--- a/components/polylith/libs/report.py
+++ b/components/polylith/libs/report.py
@@ -1,3 +1,4 @@
+import difflib
 from pathlib import Path
 from typing import Set
 
@@ -36,13 +37,22 @@ def flatten_imports(brick_imports: dict, brick: str) -> Set[str]:
     return set().union(*brick_imports.get(brick, {}).values())
 
 
-def calculate_diff(brick_imports: dict, deps: Set[str]) -> Set[str]:
+def flatten_brick_imports(brick_imports: dict) -> Set[str]:
     bases_imports = flatten_imports(brick_imports, "bases")
     components_imports = flatten_imports(brick_imports, "components")
 
-    normalized_deps = {t.replace("-", "_") for t in deps}
+    return set().union(bases_imports, components_imports)
+
+
+def filter_close_matches(unknown_imports: Set[str], deps: Set[str]) -> Set[str]:
+    return {u for u in unknown_imports if not difflib.get_close_matches(u, deps)}
+
+
+def calculate_diff(brick_imports: dict, deps: Set[str]) -> Set[str]:
+    imports = flatten_brick_imports(brick_imports)
+    unknown_imports = imports.difference(deps)
 
-    return set().union(bases_imports, components_imports).difference(normalized_deps)
+    return filter_close_matches(unknown_imports, deps)
 
 
 def print_libs_summary(brick_imports: dict, project_data: dict) -> None:
diff --git a/projects/poetry_polylith_plugin/pyproject.toml b/projects/poetry_polylith_plugin/pyproject.toml
index 2306d5d..4e6430c 100644
--- a/projects/poetry_polylith_plugin/pyproject.toml
+++ b/projects/poetry_polylith_plugin/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "poetry-polylith-plugin"
-version = "1.5.0"
+version = "1.5.1"
 description = "A Poetry plugin that adds tooling support for the Polylith Architecture"
 authors = ["David Vujic"]
 homepage = "https://davidvujic.github.io/python-polylith-docs/"
diff --git a/pyproject.toml b/pyproject.toml
index e05e24a..52310cb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -49,4 +49,4 @@ requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"
 
 [tool.pytest.ini_options]
-addopts = "-v --cov=components --cov-branch --cov-report=term-missing"
+addopts = "-sv"


Test patch:
diff --git a/test/components/polylith/libs/test_report.py b/test/components/polylith/libs/test_report.py
new file mode 100644
index 0000000..57679d4
--- /dev/null
+++ b/test/components/polylith/libs/test_report.py
@@ -0,0 +1,70 @@
+from polylith.libs import report
+
+
+def test_calculate_diff_reports_no_diff():
+    brick_imports = {
+        "bases": {"my_base": {"rich"}},
+        "components": {
+            "one": {"rich"},
+            "two": {"rich", "cleo"},
+            "thre": {"tomlkit"},
+        },
+    }
+
+    third_party_libs = {
+        "tomlkit",
+        "cleo",
+        "requests",
+        "rich",
+    }
+
+    res = report.calculate_diff(brick_imports, third_party_libs)
+
+    assert len(res) == 0
+
+
+def test_calculate_diff_should_report_missing_dependency():
+    expected_missing = "aws-lambda-powertools"
+
+    brick_imports = {
+        "bases": {"my_base": {"poetry"}},
+        "components": {
+            "one": {"tomlkit"},
+            "two": {"tomlkit", expected_missing, "rich"},
+            "three": {"rich"},
+        },
+    }
+
+    third_party_libs = {
+        "tomlkit",
+        "poetry",
+        "mypy-extensions",
+        "rich",
+    }
+
+    res = report.calculate_diff(brick_imports, third_party_libs)
+
+    assert res == {expected_missing}
+
+
+def test_calculate_diff_should_identify_close_match():
+    brick_imports = {
+        "bases": {"my_base": {"poetry"}},
+        "components": {
+            "one": {"tomlkit"},
+            "two": {"tomlkit", "aws_lambda_powertools", "rich"},
+            "three": {"rich", "pyyoutube"},
+        },
+    }
+
+    third_party_libs = {
+        "tomlkit",
+        "python-youtube",
+        "poetry",
+        "aws-lambda-powertools",
+        "rich",
+    }
+
+    res = report.calculate_diff(brick_imports, third_party_libs)
+
+    assert len(res) == 0