Skip to content

Commit

Permalink
feat: find workspace root, for repos with several sub-workspaces (#296)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
DavidVujic authored Nov 24, 2024
1 parent eb7cf13 commit 0c7b67a
Show file tree
Hide file tree
Showing 10 changed files with 92 additions and 87 deletions.
26 changes: 6 additions & 20 deletions components/polylith/configuration/core.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,11 @@
from functools import lru_cache
from pathlib import Path
from typing import List, Union

import tomlkit
from polylith import repo


@lru_cache
def _load_workspace_config(path: Path) -> tomlkit.TOMLDocument:
fullpath = path / repo.workspace_file

if not fullpath.exists():
fullpath = path / repo.default_toml

content = fullpath.read_text()

return tomlkit.loads(content)


def get_namespace_from_config(path: Path) -> str:
toml: dict = _load_workspace_config(path)
toml: dict = repo.load_workspace_config(path)

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

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


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

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

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


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

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


def is_test_generation_enabled(path: Path) -> bool:
toml: dict = _load_workspace_config(path)
toml: dict = repo.load_workspace_config(path)

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


def is_readme_generation_enabled(path: Path) -> bool:
toml: dict = _load_workspace_config(path)
toml: dict = repo.load_workspace_config(path)

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


def get_theme_from_config(path: Path) -> str:
toml: dict = _load_workspace_config(path)
toml: dict = repo.load_workspace_config(path)

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

Expand Down
2 changes: 2 additions & 0 deletions components/polylith/repo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
is_pdm,
is_pep_621_ready,
is_poetry,
load_workspace_config,
projects_dir,
readme_file,
workspace_file,
Expand All @@ -26,6 +27,7 @@
"is_pdm",
"is_pep_621_ready",
"is_poetry",
"load_workspace_config",
"projects_dir",
"readme_file",
"workspace_file",
Expand Down
54 changes: 51 additions & 3 deletions components/polylith/repo/repo.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from functools import lru_cache
from pathlib import Path
from typing import Union

import tomlkit

workspace_file = "workspace.toml"
root_file = ".git"
default_toml = "pyproject.toml"
Expand All @@ -12,6 +15,38 @@
development_dir = "development"


def load_content(fullpath: Path) -> tomlkit.TOMLDocument:
content = fullpath.read_text()

return tomlkit.loads(content)


@lru_cache
def load_root_project_config(path: Path) -> tomlkit.TOMLDocument:
fullpath = path / default_toml

return load_content(fullpath)


def has_workspace_config(data: tomlkit.TOMLDocument) -> bool:
ns = data.get("tool", {}).get("polylith", {}).get("namespace")

return True if ns else False


@lru_cache
def load_workspace_config(path: Path) -> tomlkit.TOMLDocument:
fullpath = path / workspace_file

if fullpath.exists():
content = load_content(fullpath)

if has_workspace_config(content):
return content

return load_root_project_config(path)


def is_drive_root(cwd: Path) -> bool:
return cwd == Path(cwd.root) or cwd == cwd.parent

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


def is_python_workspace_root(path: Path) -> bool:
data = load_root_project_config(path)

return has_workspace_config(data)


def find_upwards(cwd: Path, name: str) -> Union[Path, None]:
if is_drive_root(cwd):
return None

fullpath = cwd / name

if fullpath.exists():
return fullpath
if name == workspace_file:
return fullpath

return fullpath if is_python_workspace_root(cwd) else None

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

def find_workspace_root(cwd: Path) -> Union[Path, None]:
workspace_root = find_upwards_dir(cwd, workspace_file)

if workspace_root:
return workspace_root
return find_upwards_dir(cwd, root_file)

repo_root = find_upwards_dir(cwd, root_file)

return repo_root or find_upwards_dir(cwd, default_toml)


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

if not root:
raise ValueError(
"Didn't find the workspace root. Expected to find a workspace.toml or .git file."
"Didn't find the workspace root. Expected to find a workspace.toml or pyproject.toml with Workspace config."
)

return root
Expand Down
2 changes: 1 addition & 1 deletion projects/hatch_polylith_bricks/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "hatch-polylith-bricks"
version = "1.2.8"
version = "1.2.9"
description = "Hatch build hook plugin for Polylith"
authors = ['David Vujic']
homepage = "https://davidvujic.github.io/python-polylith-docs/"
Expand Down
2 changes: 1 addition & 1 deletion projects/pdm_polylith_bricks/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pdm-polylith-bricks"
version = "1.0.9"
version = "1.1.0"
description = "a PDM build hook for Polylith"
authors = ["David Vujic"]
homepage = "https://davidvujic.github.io/python-polylith-docs/"
Expand Down
2 changes: 1 addition & 1 deletion projects/pdm_polylith_workspace/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pdm-polylith-workspace"
version = "1.0.9"
version = "1.1.0"
description = "a PDM build hook for a Polylith workspace"
homepage = "https://davidvujic.github.io/python-polylith-docs/"
repository = "https://github.com/davidvujic/python-polylith"
Expand Down
2 changes: 1 addition & 1 deletion projects/poetry_polylith_plugin/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "poetry-polylith-plugin"
version = "1.33.0"
version = "1.34.0"
description = "A Poetry plugin that adds tooling support for the Polylith Architecture"
authors = ["David Vujic"]
homepage = "https://davidvujic.github.io/python-polylith-docs/"
Expand Down
2 changes: 1 addition & 1 deletion projects/polylith_cli/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "polylith-cli"
version = "1.22.0"
version = "1.23.0"
description = "Python tooling support for the Polylith Architecture"
authors = ['David Vujic']
homepage = "https://davidvujic.github.io/python-polylith-docs/"
Expand Down
4 changes: 2 additions & 2 deletions test/components/polylith/configuration/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ def patch(theme: str, tag_sorting: Optional[List[str]] = None):
config = config_template.format(
theme=theme, tag_sorting=_tag_sorting(tag_sorting)
)
name = "_load_workspace_config"
name = "load_workspace_config"

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

return patch

Expand Down
83 changes: 26 additions & 57 deletions test/components/polylith/libs/test_report.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import pytest
from polylith.libs import report

third_party_libs = {
"cleo",
"mypy-extensions",
"poetry",
"tomlkit",
"requests",
"rich",
}


def test_calculate_diff_reports_no_diff():
brick_imports = {
Expand All @@ -11,13 +21,6 @@ def test_calculate_diff_reports_no_diff():
},
}

third_party_libs = {
"tomlkit",
"cleo",
"requests",
"rich",
}

res = report.calculate_diff(brick_imports, third_party_libs)

assert len(res) == 0
Expand All @@ -35,67 +38,33 @@ def test_calculate_diff_should_report_missing_dependency():
},
}

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():
@pytest.mark.parametrize(
"imports, is_strict",
[
({"aws_lambda_powertools", "PIL", "pyyoutube"}, False),
({"typing_extensions"}, True),
],
)
def test_calculate_diff_should_identify_close_match(imports: set, is_strict: bool):
brick_imports = {
"bases": {"my_base": {"poetry"}},
"components": {
"one": {"tomlkit"},
"two": {"tomlkit", "aws_lambda_powertools", "rich"},
"three": {"rich", "pyyoutube"},
},
"bases": {"thebase": {"typer"}},
"components": {"one": imports},
}

third_party_libs = {
"tomlkit",
"python-youtube",
"poetry",
libs = {
"aws-lambda-powertools",
"rich",
}

res = report.calculate_diff(brick_imports, third_party_libs)

assert len(res) == 0


def test_calculate_diff_should_identify_close_match_case_insensitive():
brick_imports = {
"bases": {"my_base": {}},
"components": {
"one": {"PIL"},
},
}

third_party_libs = {"pillow"}

res = report.calculate_diff(brick_imports, third_party_libs)

assert len(res) == 0


def test_calculate_diff_strict_should_identify_close_match_for_dash_and_low_dash():
brick_imports = {
"bases": {"thebase": {"typer"}},
"components": {
"one": {"typing_extensions"},
},
"pillow",
"python-youtube",
"typer",
"typing-extensions",
}

third_party_libs = {"typer", "typing-extensions"}

res = report.calculate_diff(brick_imports, third_party_libs, True)
res = report.calculate_diff(brick_imports, libs, is_strict)

assert len(res) == 0

Expand Down

0 comments on commit 0c7b67a

Please sign in to comment.