Skip to content

Commit 0c7b67a

Browse files
authored
feat: find workspace root, for repos with several sub-workspaces (#296)
* feat: find workspace root, for repos without a workspace.toml and for repos where the Workspace root is not the same as the git root * fix: error message when workspace not found * bump Hatch hook to 1.2.9 * bump PDM workspace and brick hooks to 1.1.0 * bump Poetry plugin to 1.34.0 * bump CLI to 1.23.0 * refactor(libs test): less duplications * fix(workspace root): check if workspace.toml contain the expected config, otherwise return pyproject contents
1 parent eb7cf13 commit 0c7b67a

File tree

10 files changed

+92
-87
lines changed

10 files changed

+92
-87
lines changed

components/polylith/configuration/core.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
from functools import lru_cache
21
from pathlib import Path
32
from typing import List, Union
43

5-
import tomlkit
64
from polylith import repo
75

86

9-
@lru_cache
10-
def _load_workspace_config(path: Path) -> tomlkit.TOMLDocument:
11-
fullpath = path / repo.workspace_file
12-
13-
if not fullpath.exists():
14-
fullpath = path / repo.default_toml
15-
16-
content = fullpath.read_text()
17-
18-
return tomlkit.loads(content)
19-
20-
217
def get_namespace_from_config(path: Path) -> str:
22-
toml: dict = _load_workspace_config(path)
8+
toml: dict = repo.load_workspace_config(path)
239

2410
return toml["tool"]["polylith"]["namespace"]
2511

@@ -30,7 +16,7 @@ def get_git_tag_pattern(toml: dict) -> str:
3016

3117

3218
def get_tag_pattern_from_config(path: Path, key: Union[str, None]) -> Union[str, None]:
33-
toml: dict = _load_workspace_config(path)
19+
toml: dict = repo.load_workspace_config(path)
3420

3521
patterns = toml["tool"]["polylith"].get("tag", {}).get("patterns")
3622

@@ -41,7 +27,7 @@ def get_tag_pattern_from_config(path: Path, key: Union[str, None]) -> Union[str,
4127

4228

4329
def get_tag_sort_options_from_config(path: Path) -> List[str]:
44-
toml: dict = _load_workspace_config(path)
30+
toml: dict = repo.load_workspace_config(path)
4531

4632
options = toml["tool"]["polylith"].get("tag", {}).get("sorting")
4733
# Default sorting option
@@ -51,21 +37,21 @@ def get_tag_sort_options_from_config(path: Path) -> List[str]:
5137

5238

5339
def is_test_generation_enabled(path: Path) -> bool:
54-
toml: dict = _load_workspace_config(path)
40+
toml: dict = repo.load_workspace_config(path)
5541

5642
enabled = toml["tool"]["polylith"]["test"]["enabled"]
5743
return bool(enabled)
5844

5945

6046
def is_readme_generation_enabled(path: Path) -> bool:
61-
toml: dict = _load_workspace_config(path)
47+
toml: dict = repo.load_workspace_config(path)
6248

6349
enabled = toml["tool"]["polylith"].get("resources", {}).get("brick_docs_enabled")
6450
return bool(enabled)
6551

6652

6753
def get_theme_from_config(path: Path) -> str:
68-
toml: dict = _load_workspace_config(path)
54+
toml: dict = repo.load_workspace_config(path)
6955

7056
return toml["tool"]["polylith"]["structure"].get("theme") or "tdd"
7157

components/polylith/repo/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
is_pdm,
1010
is_pep_621_ready,
1111
is_poetry,
12+
load_workspace_config,
1213
projects_dir,
1314
readme_file,
1415
workspace_file,
@@ -26,6 +27,7 @@
2627
"is_pdm",
2728
"is_pep_621_ready",
2829
"is_poetry",
30+
"load_workspace_config",
2931
"projects_dir",
3032
"readme_file",
3133
"workspace_file",

components/polylith/repo/repo.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
from functools import lru_cache
12
from pathlib import Path
23
from typing import Union
34

5+
import tomlkit
6+
47
workspace_file = "workspace.toml"
58
root_file = ".git"
69
default_toml = "pyproject.toml"
@@ -12,6 +15,38 @@
1215
development_dir = "development"
1316

1417

18+
def load_content(fullpath: Path) -> tomlkit.TOMLDocument:
19+
content = fullpath.read_text()
20+
21+
return tomlkit.loads(content)
22+
23+
24+
@lru_cache
25+
def load_root_project_config(path: Path) -> tomlkit.TOMLDocument:
26+
fullpath = path / default_toml
27+
28+
return load_content(fullpath)
29+
30+
31+
def has_workspace_config(data: tomlkit.TOMLDocument) -> bool:
32+
ns = data.get("tool", {}).get("polylith", {}).get("namespace")
33+
34+
return True if ns else False
35+
36+
37+
@lru_cache
38+
def load_workspace_config(path: Path) -> tomlkit.TOMLDocument:
39+
fullpath = path / workspace_file
40+
41+
if fullpath.exists():
42+
content = load_content(fullpath)
43+
44+
if has_workspace_config(content):
45+
return content
46+
47+
return load_root_project_config(path)
48+
49+
1550
def is_drive_root(cwd: Path) -> bool:
1651
return cwd == Path(cwd.root) or cwd == cwd.parent
1752

@@ -22,14 +57,23 @@ def is_repo_root(cwd: Path) -> bool:
2257
return fullpath.exists()
2358

2459

60+
def is_python_workspace_root(path: Path) -> bool:
61+
data = load_root_project_config(path)
62+
63+
return has_workspace_config(data)
64+
65+
2566
def find_upwards(cwd: Path, name: str) -> Union[Path, None]:
2667
if is_drive_root(cwd):
2768
return None
2869

2970
fullpath = cwd / name
3071

3172
if fullpath.exists():
32-
return fullpath
73+
if name == workspace_file:
74+
return fullpath
75+
76+
return fullpath if is_python_workspace_root(cwd) else None
3377

3478
if is_repo_root(cwd):
3579
return None
@@ -45,17 +89,21 @@ def find_upwards_dir(cwd: Path, name: str) -> Union[Path, None]:
4589

4690
def find_workspace_root(cwd: Path) -> Union[Path, None]:
4791
workspace_root = find_upwards_dir(cwd, workspace_file)
92+
4893
if workspace_root:
4994
return workspace_root
50-
return find_upwards_dir(cwd, root_file)
95+
96+
repo_root = find_upwards_dir(cwd, root_file)
97+
98+
return repo_root or find_upwards_dir(cwd, default_toml)
5199

52100

53101
def get_workspace_root(cwd: Path) -> Path:
54102
root = find_workspace_root(cwd)
55103

56104
if not root:
57105
raise ValueError(
58-
"Didn't find the workspace root. Expected to find a workspace.toml or .git file."
106+
"Didn't find the workspace root. Expected to find a workspace.toml or pyproject.toml with Workspace config."
59107
)
60108

61109
return root

projects/hatch_polylith_bricks/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 = "hatch-polylith-bricks"
3-
version = "1.2.8"
3+
version = "1.2.9"
44
description = "Hatch build hook plugin for Polylith"
55
authors = ['David Vujic']
66
homepage = "https://davidvujic.github.io/python-polylith-docs/"

projects/pdm_polylith_bricks/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 = "pdm-polylith-bricks"
3-
version = "1.0.9"
3+
version = "1.1.0"
44
description = "a PDM build hook for Polylith"
55
authors = ["David Vujic"]
66
homepage = "https://davidvujic.github.io/python-polylith-docs/"

projects/pdm_polylith_workspace/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 = "pdm-polylith-workspace"
3-
version = "1.0.9"
3+
version = "1.1.0"
44
description = "a PDM build hook for a Polylith workspace"
55
homepage = "https://davidvujic.github.io/python-polylith-docs/"
66
repository = "https://github.com/davidvujic/python-polylith"

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.33.0"
3+
version = "1.34.0"
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.22.0"
3+
version = "1.23.0"
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/configuration/test_core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ def patch(theme: str, tag_sorting: Optional[List[str]] = None):
4141
config = config_template.format(
4242
theme=theme, tag_sorting=_tag_sorting(tag_sorting)
4343
)
44-
name = "_load_workspace_config"
44+
name = "load_workspace_config"
4545

46-
monkeypatch.setattr(core, name, lambda *args: tomlkit.loads(config))
46+
monkeypatch.setattr(core.repo, name, lambda *args: tomlkit.loads(config))
4747

4848
return patch
4949

test/components/polylith/libs/test_report.py

Lines changed: 26 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
1+
import pytest
12
from polylith.libs import report
23

4+
third_party_libs = {
5+
"cleo",
6+
"mypy-extensions",
7+
"poetry",
8+
"tomlkit",
9+
"requests",
10+
"rich",
11+
}
12+
313

414
def test_calculate_diff_reports_no_diff():
515
brick_imports = {
@@ -11,13 +21,6 @@ def test_calculate_diff_reports_no_diff():
1121
},
1222
}
1323

14-
third_party_libs = {
15-
"tomlkit",
16-
"cleo",
17-
"requests",
18-
"rich",
19-
}
20-
2124
res = report.calculate_diff(brick_imports, third_party_libs)
2225

2326
assert len(res) == 0
@@ -35,67 +38,33 @@ def test_calculate_diff_should_report_missing_dependency():
3538
},
3639
}
3740

38-
third_party_libs = {
39-
"tomlkit",
40-
"poetry",
41-
"mypy-extensions",
42-
"rich",
43-
}
44-
4541
res = report.calculate_diff(brick_imports, third_party_libs)
4642

4743
assert res == {expected_missing}
4844

4945

50-
def test_calculate_diff_should_identify_close_match():
46+
@pytest.mark.parametrize(
47+
"imports, is_strict",
48+
[
49+
({"aws_lambda_powertools", "PIL", "pyyoutube"}, False),
50+
({"typing_extensions"}, True),
51+
],
52+
)
53+
def test_calculate_diff_should_identify_close_match(imports: set, is_strict: bool):
5154
brick_imports = {
52-
"bases": {"my_base": {"poetry"}},
53-
"components": {
54-
"one": {"tomlkit"},
55-
"two": {"tomlkit", "aws_lambda_powertools", "rich"},
56-
"three": {"rich", "pyyoutube"},
57-
},
55+
"bases": {"thebase": {"typer"}},
56+
"components": {"one": imports},
5857
}
5958

60-
third_party_libs = {
61-
"tomlkit",
62-
"python-youtube",
63-
"poetry",
59+
libs = {
6460
"aws-lambda-powertools",
65-
"rich",
66-
}
67-
68-
res = report.calculate_diff(brick_imports, third_party_libs)
69-
70-
assert len(res) == 0
71-
72-
73-
def test_calculate_diff_should_identify_close_match_case_insensitive():
74-
brick_imports = {
75-
"bases": {"my_base": {}},
76-
"components": {
77-
"one": {"PIL"},
78-
},
79-
}
80-
81-
third_party_libs = {"pillow"}
82-
83-
res = report.calculate_diff(brick_imports, third_party_libs)
84-
85-
assert len(res) == 0
86-
87-
88-
def test_calculate_diff_strict_should_identify_close_match_for_dash_and_low_dash():
89-
brick_imports = {
90-
"bases": {"thebase": {"typer"}},
91-
"components": {
92-
"one": {"typing_extensions"},
93-
},
61+
"pillow",
62+
"python-youtube",
63+
"typer",
64+
"typing-extensions",
9465
}
9566

96-
third_party_libs = {"typer", "typing-extensions"}
97-
98-
res = report.calculate_diff(brick_imports, third_party_libs, True)
67+
res = report.calculate_diff(brick_imports, libs, is_strict)
9968

10069
assert len(res) == 0
10170

0 commit comments

Comments
 (0)