Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: find workspace root, for repos with several sub-workspaces #296

Merged
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

Check notice on line 1 in test/components/polylith/libs/test_report.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

✅ No longer an issue: Code Duplication

The module no longer contains too many functions with similar structure
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 @@
},
}

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 @@
},
}

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