Skip to content

Commit ec73971

Browse files
authored
fix(poly check): dependency/dists lookup lookup performance improvements (#359)
* fix(poly check): cached 'packages_distributions' from importlib.metadata, always run with same input * fix(poly check): local cache for dist.files lookup when figuring out dependencies and sub-dependencies * refactor(distributions): extract custom caching into separate module, also: unit tests resetting cached state before run * bump Poetry plugin to 1.38.2 * bump CLI to 1.31.3
1 parent 7c4c4b8 commit ec73971

File tree

6 files changed

+52
-15
lines changed

6 files changed

+52
-15
lines changed

components/polylith/distributions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from polylith.distributions import caching
12
from polylith.distributions.collect import known_aliases_and_sub_dependencies
23
from polylith.distributions.core import (
34
distributions_packages,
@@ -6,6 +7,7 @@
67
)
78

89
__all__ = [
10+
"caching",
911
"distributions_packages",
1012
"distributions_sub_packages",
1113
"get_distributions",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
_cache = {}
2+
3+
4+
def add(key: str, value) -> None:
5+
_cache[key] = value
6+
7+
8+
def get(key: str):
9+
return _cache[key]
10+
11+
12+
def exists(key: str) -> bool:
13+
return key in _cache
14+
15+
16+
def clear() -> None:
17+
_cache.clear()

components/polylith/distributions/core.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import importlib.metadata
2+
import pathlib
23
import re
34
from functools import lru_cache, reduce
45
from typing import Dict, List
56

7+
from polylith.distributions import caching
8+
69
SUB_DEP_SEPARATORS = r"[\s!=;><\^~]"
710

811

@@ -25,9 +28,11 @@ def map_sub_packages(acc, dist) -> dict:
2528
return {**acc, **dist_subpackages(dist)}
2629

2730

28-
def parsed_namespaces_from_files(dist) -> List[str]:
29-
name = dist.metadata["name"]
30-
files = dist.files or []
31+
def parsed_namespaces_from_files(dist, name: str) -> List[str]:
32+
if not caching.exists(name):
33+
files = dist.files or []
34+
python_files = [f for f in files if f.suffix == ".py"]
35+
caching.add(name, python_files)
3136

3237
normalized_name = str.replace(name, "-", "_")
3338
to_ignore = {
@@ -38,7 +43,7 @@ def parsed_namespaces_from_files(dist) -> List[str]:
3843
"..",
3944
}
4045

41-
filtered = [f for f in files if f.suffix == ".py"]
46+
filtered: List[pathlib.PurePosixPath] = caching.get(name)
4247
top_folders = {f.parts[0] for f in filtered if len(f.parts) > 1}
4348
namespaces = {t for t in top_folders if t not in to_ignore}
4449

@@ -49,17 +54,19 @@ def parsed_top_level_namespace(namespaces: List[str]) -> List[str]:
4954
return [str.replace(ns, "/", ".") for ns in namespaces]
5055

5156

52-
def top_level_packages(dist) -> List[str]:
57+
def top_level_packages(dist, name: str) -> List[str]:
5358
top_level = dist.read_text("top_level.txt")
5459

5560
namespaces = str.split(top_level or "")
5661

57-
return parsed_top_level_namespace(namespaces) or parsed_namespaces_from_files(dist)
62+
return parsed_top_level_namespace(namespaces) or parsed_namespaces_from_files(
63+
dist, name
64+
)
5865

5966

6067
def mapped_packages(dist) -> dict:
61-
packages = top_level_packages(dist)
6268
name = dist.metadata["name"]
69+
packages = top_level_packages(dist, name)
6370

6471
return {name: packages} if packages else {}
6572

@@ -83,6 +90,14 @@ def get_distributions() -> list:
8390
return list(importlib.metadata.distributions())
8491

8592

93+
@lru_cache
94+
def package_distributions_from_importlib() -> dict:
95+
# added in Python 3.10
96+
fn = getattr(importlib.metadata, "packages_distributions", None)
97+
98+
return fn() if fn else {}
99+
100+
86101
def get_packages_distributions(project_dependencies: set) -> set:
87102
"""Return the mapped top namespace from an import
88103
@@ -93,10 +108,7 @@ def get_packages_distributions(project_dependencies: set) -> set:
93108
Note: available for Python >= 3.10
94109
"""
95110

96-
# added in Python 3.10
97-
fn = getattr(importlib.metadata, "packages_distributions", None)
98-
99-
dists = fn() if fn else {}
111+
dists = package_distributions_from_importlib()
100112

101113
common = {k for k, v in dists.items() if project_dependencies.intersection(set(v))}
102114

projects/poetry_polylith_plugin/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "poetry-polylith-plugin"
3-
version = "1.38.1"
3+
version = "1.38.2"
44
description = "A Poetry plugin that adds tooling support for the Polylith Architecture"
55
authors = ["David Vujic"]
66
homepage = "https://davidvujic.github.io/python-polylith-docs/"

projects/polylith_cli/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "polylith-cli"
3-
version = "1.31.2"
3+
version = "1.31.3"
44
description = "Python tooling support for the Polylith Architecture"
55
authors = ['David Vujic']
66
homepage = "https://davidvujic.github.io/python-polylith-docs/"

test/components/polylith/distributions/test_core.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ def read_text(self, *args):
2121
return self.read_text_data
2222

2323

24+
@pytest.fixture
25+
def setup():
26+
distributions.caching.clear()
27+
distributions.core.package_distributions_from_importlib.cache_clear()
28+
29+
2430
def test_distribution_packages():
2531
dists = list(importlib.metadata.distributions())
2632

@@ -61,7 +67,7 @@ def test_distribution_packages_for_missing_metadata_is_handled():
6167
assert res == {}
6268

6369

64-
def test_distribution_packages_with_top_level_ns_information_in_files():
70+
def test_distribution_packages_with_top_level_ns_information_in_files(setup):
6571
files = [
6672
importlib.metadata.PackagePath("some_module.py"),
6773
importlib.metadata.PackagePath("hello/world.py"),
@@ -104,7 +110,7 @@ def test_distribution_sub_packages():
104110

105111

106112
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
107-
def test_package_distributions_returning_top_namespace(monkeypatch):
113+
def test_package_distributions_returning_top_namespace(setup, monkeypatch):
108114
fake_dists = {
109115
"something": ["something-subnamespace"],
110116
"opentelemetry": ["opentelemetry-instrumentation-fastapi"],

0 commit comments

Comments
 (0)