Skip to content

Commit

Permalink
fix dep syncing
Browse files Browse the repository at this point in the history
  • Loading branch information
carderne committed Aug 26, 2024
1 parent f820c55 commit ab6eaac
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 105 deletions.
72 changes: 28 additions & 44 deletions una/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,27 @@


def check_package_deps(root: Path, ns: str, package: PackageDeps, alias: list[str]) -> CheckDiff:
int_dep_imports, ext_dep_imports = _collect_all_imports(root, ns, package)
dep_pkgs = {c for c in package.int_deps}
all_paths = [c.path for c in package_deps.get_package_confs(root)]
dep_paths = {p for p in all_paths if p.name in {d.name for d in dep_pkgs}}

all_imports = parse.fetch_all_imports(dep_paths)
int_dep_imports = _get_int_dep_imports(all_imports, root, ns)
ext_dep_imports = _get_ext_dep_imports(all_imports, ns)
ext_dep_imports = {k: v for k, v in ext_dep_imports.items() if k == package.name}

external_deps = distributions.collect_deps(package.ext_deps, alias)
diff = _create_diff(
package,
int_dep_imports,
ext_dep_imports,
external_deps,
int_deps = {c.name for c in package.int_deps}
int_dep_diff: set[str] = set().union(*int_dep_imports.values()).difference(int_deps)
ext_dep_diff = _ext_dep_diff(ext_dep_imports, external_deps)

return CheckDiff(
package=package,
int_dep_imports=int_dep_imports,
ext_dep_imports=ext_dep_imports,
int_dep_diff=int_dep_diff,
ext_dep_diff=ext_dep_diff,
)
return diff


def _extract_int_deps(paths: set[Path], ns: str) -> Imports:
Expand All @@ -37,65 +49,37 @@ def _with_unknown_deps(root: Path, ns: str, int_dep_imports: Imports) -> Imports
return _with_unknown_deps(root, ns, collected)


def _only_int_dep_imports(imports: set[str], top_ns: str) -> set[str]:
return {i for i in imports if i.startswith(top_ns)}
def _only_int_dep_imports(imports: set[str], ns: str) -> set[str]:
return {i for i in imports if i.startswith(ns)}


def _only_int_dep_name(int_dep_imports: set[str]) -> set[str]:
res = [i.split(".") for i in int_dep_imports]
return {i[1] for i in res if len(i) > 1}


def _extract_int_dep_imports(all_imports: Imports, top_ns: str) -> Imports:
only_int = {k: _only_int_dep_imports(v, top_ns) for k, v in all_imports.items()}
def _extract_int_dep_imports(all_imports: Imports, ns: str) -> Imports:
only_int = {k: _only_int_dep_imports(v, ns) for k, v in all_imports.items()}
return {k: _only_int_dep_name(v) for k, v in only_int.items() if v}


def _fetch_int_dep_imports(root: Path, ns: str, all_imports: Imports) -> Imports:
def _get_int_dep_imports(all_imports: Imports, root: Path, ns: str) -> Imports:
extracted = _extract_int_dep_imports(all_imports, ns)
res = _with_unknown_deps(root, ns, extracted)
return res


def _collect_all_imports(root: Path, ns: str, package: PackageDeps) -> tuple[Imports, Imports]:
dep_pkgs = {c for c in package.int_deps}
all_paths = [c.path for c in package_deps.get_package_confs(root)]
dep_paths = {p for p in all_paths if p.name in {d.name for d in dep_pkgs}}
all_imports_in_deps = parse.fetch_all_imports(dep_paths)
int_dep_imports = _fetch_int_dep_imports(root, ns, all_imports_in_deps)
ext_dep_imports = _extract_ext_dep_imports(all_imports_in_deps, ns)
return int_dep_imports, ext_dep_imports


def _extract_top_ns_from_imports(imports: set[str]) -> set[str]:
def _extract_ns_from_imports(imports: set[str]) -> set[str]:
return {imp.split(".")[0] for imp in imports}


def _extract_ext_dep_imports(all_imports: Imports, top_ns: str) -> Imports:
top_level_imports = {k: _extract_top_ns_from_imports(v) for k, v in all_imports.items()}
to_exclude = stdlib.get_stdlib().union({top_ns})
def _get_ext_dep_imports(all_imports: Imports, ns: str) -> Imports:
top_level_imports = {k: _extract_ns_from_imports(v) for k, v in all_imports.items()}
to_exclude = stdlib.get_stdlib().union({ns})
with_third_party = {k: v - to_exclude for k, v in top_level_imports.items()}
return {k: v for k, v in with_third_party.items() if v}


def _create_diff(
package: PackageDeps,
int_dep_imports: Imports,
ext_dep_imports: Imports,
external_deps: set[str],
) -> CheckDiff:
int_deps = {c for c in package.int_deps}
int_dep_diff: set[str] = set().union(*int_dep_imports.values()).difference(int_deps)
ext_dep_diff = _ext_dep_diff(ext_dep_imports, external_deps)
return CheckDiff(
package=package,
int_dep_imports=int_dep_imports,
ext_dep_imports=ext_dep_imports,
int_dep_diff=int_dep_diff,
ext_dep_diff=ext_dep_diff,
)


def _ext_dep_diff(imports: Imports, deps: set[str]) -> set[str]:
deps_imports: set[str] = set().union(*imports.values())
unknown_imports = deps_imports.difference(deps)
Expand Down
64 changes: 22 additions & 42 deletions una/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@


def rich_console() -> Console:
theme = Theme(
{
"dat": "#999966",
"pkg": "#8A2BE2",
"dep": "#32CD32",
}
)
theme = Theme({"pkg": "#8A2BE2", "dep": "#32CD32"})
return Console(theme=theme)


Expand All @@ -36,53 +30,39 @@ def sync_command(
str, Option(help="alias for third-party libraries, map install to import name")
] = "",
):
"""Update pyproject.toml with missing int_deps."""
"""Update packages with missing dependencies."""
root = config.get_workspace_root()
ns = config.get_ns(root)
alias_list = alias.split(",") if alias else []

packages = package_deps.get_packages(root, ns)
diffs: list[CheckDiff] = []
for p in packages:
diff = check.check_package_deps(root, ns, p, alias_list)
diffs.append(diff)
d = check.check_package_deps(root, ns, p, alias_list)
diffs.append(d)

console = rich_console()
if check_only:
failed = any(d.int_dep_diff or d.ext_dep_diff for d in diffs)
if failed:
for d in diffs:
console = rich_console()
missing_ext = ", ".join(sorted(d.ext_dep_diff))
missing_int = ", ".join(sorted(d.int_dep_diff))
console.print(f"Cannot locate {missing_ext} in {d.package.name}")
console.print(f"Cannot locate {missing_int} in {d.package.name}")
raise Exit(code=1)

else:
for d in diffs:
sync.sync_package_int_deps(d, ns)

if not quiet:
_print_summary(d)
_print_int_dep_imports(d)


def _print_int_dep_imports(diff: CheckDiff) -> None:
console = rich_console()
for key, values in diff.int_dep_imports.items():
imports_in_int_dep = values.difference({key})
if not imports_in_int_dep:
continue
joined = ", ".join(imports_in_int_dep)
message = f":information: [dat]{key}[/] is importing [dat]{joined}[/]"
console.print(message)
if d.ext_dep_diff:
missing = ", ".join(sorted(d.ext_dep_diff))
console.print(f"[pkg]{d.package.name}[/] can't find external: [dep]{missing}[/]")
if d.int_dep_diff:
missing = ", ".join(sorted(d.int_dep_diff))
console.print(f"[pkg]{d.package.name}[/] can't find internal: [dep]{missing}[/]")

if any(d.int_dep_diff or d.ext_dep_diff for d in diffs):
raise Exit(code=1)
raise Exit()

for d in diffs:
sync.sync_package(root, d, ns)
if not quiet:
for c in d.int_dep_diff:
console.print(f"[pkg]{d.package.name}[/] adding dep [dep]{c}[/]")

def _print_summary(diff: CheckDiff) -> None:
console = rich_console()
name = diff.package.name
for c in diff.int_dep_diff:
console.print(f"adding dep [dep]{c}[/] to [pkg]{name}[/]")
if not quiet:
console.print("All good!")


@create.command("package")
Expand Down
4 changes: 2 additions & 2 deletions una/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
[tool.hatch.metadata.hooks.una-meta]
[tool.una.deps]
{internal_deps}
{internal_deps}\
"""

_EXAMPLE_INTERNAL_DEPS = """\
Expand Down Expand Up @@ -151,7 +151,7 @@ def _update_root_pyproj(path: Path, ns: str, dependencies: str) -> None:
toml = tomlkit.parse(f.read())

toml["tool"]["rye"]["virtual"] = True # pyright:ignore[reportIndexIssue]
toml["tool"]["rye"]["workspace"] = {"member": _EXAMPLE_MEMBERS} # pyright:ignore[reportIndexIssue]
toml["tool"]["rye"]["workspace"] = {"members": _EXAMPLE_MEMBERS} # pyright:ignore[reportIndexIssue]
toml["tool"]["una"] = {"members": _EXAMPLE_MEMBERS} # pyright:ignore[reportIndexIssue]
with pyproj.open("w") as f:
f.write(tomlkit.dumps(toml)) # pyright:ignore[reportUnknownMemberType]
11 changes: 6 additions & 5 deletions una/package_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ def _get_package_int_deps(
all_confs: list[ConfWrapper],
namespace: str,
) -> list[IntDep]:
all_paths = [str(c.path) for c in all_confs]
packages = [Include(src=k, dst=v) for k, v in conf.conf.tool.una.deps.items()]
sorted_packages = sorted(packages, key=lambda p: p.src)
paths = [Path(p.src) for p in sorted_packages]
paths_in_namespace = [p.name for p in paths if p.parent.name == namespace]
pkg_deps_paths = sorted(list(set(all_paths).intersection(paths_in_namespace)))
paths = [(conf.path / p.src).parents[1].resolve() for p in packages]

all_paths = {Path(c.path) for c in all_confs}
pkg_deps_paths = sorted(all_paths.intersection(paths))
pkg_deps = [IntDep(path=Path(p), name=Path(p).name) for p in pkg_deps_paths]

# add self
pkg_deps.append(IntDep(path=conf.path, name=conf.conf.project.name))
return pkg_deps
40 changes: 28 additions & 12 deletions una/sync.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
from pathlib import Path
from typing import cast

from una import config, consts
from una.types import CheckDiff, Conf, Include, PackageDeps
from una import config, consts, package_deps
from una.types import CheckDiff, Conf, Include


def sync_package_int_deps(diff: CheckDiff, ns: str):
packages = [_to_package(diff.package, ns, c) for c in diff.int_dep_diff]
if packages:
_rewrite_package_pyproj(diff.package.path, packages)
def sync_package(root: Path, diff: CheckDiff, ns: str):
all_confs = package_deps.get_package_confs(root)
pkg_map = {c.path.name: c.path for c in all_confs}
packages = [_to_package(pkg_map, ns, name, diff.package.path) for name in diff.int_dep_diff]
_rewrite_package_pyproj(diff.package.path, packages)


def _to_package(orig_pkg: PackageDeps, ns: str, name: str) -> Include:
int_dep_roots = [f for f in orig_pkg.int_deps if f.name == name]
if len(int_dep_roots) != 1:
raise ValueError("WTF?") # TODO
root = int_dep_roots[0].path
src = root / name / ns / name
def _to_package(pkg_map: dict[str, Path], ns: str, name: str, caller_path: Path) -> Include:
dst = Path(ns) / name
src = _path_relative_to(pkg_map[name] / dst, caller_path)
return Include(src=str(src), dst=str(dst))


Expand All @@ -34,3 +32,21 @@ def _rewrite_package_pyproj(path: Path, packages: list[Include]):
fullpath = path / consts.PYPROJ_FILE
with fullpath.open("w", encoding="utf-8") as f:
f.write(generated)


def _path_relative_to(p: Path, other: Path) -> Path:
"""
Return relative path between paths.
Added here since the walk_up parameter isn't available in Python 3.11
https://github.com/python/cpython/blob/33d9e27b2b26d5434d654ef8e5fae560beb68b1b/Lib/pathlib.py#L663
"""
for step, path in enumerate([other] + list(other.parents)):
if p.is_relative_to(path):
break
elif path.name == "..":
raise ValueError(f"'..' segment in {str(other)!r} cannot be walked")
else:
raise ValueError(f"{str(p)!r} and {str(other)!r} have different anchors")
parts = cast(str, [".."] * step + p._tail[len(path._tail) :]) # pyright:ignore[reportUnknownMemberType,reportAttributeAccessIssue,reportUnknownArgumentType]
return Path(*parts)

0 comments on commit ab6eaac

Please sign in to comment.