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

use uv.sources table for internal deps #4

Merged
merged 1 commit into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,4 @@ jobs:
- run: |
uvx --from build pyproject-build --installer uv --outdir=dist una
uvx --from build pyproject-build --installer uv --outdir=dist plugins/hatch
uvx --from build pyproject-build --installer uv --outdir=dist plugins/pdm
- uses: pypa/gh-action-pypi-publish@release/v1
17 changes: 8 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,20 @@

</div>

Una is a tool to make Python monorepos easier. It is a CLI tool and a build plugin that does the following things:
Una is a tool to make Python monorepos with [uv](https://docs.astral.sh/uv/) easier.
It is a CLI tool and a build plugin that does the following things:

1. Enable builds of individual apps or projects within a monorepo.
2. Ensure that internal and external dependencies are correctly specified.

Una doesn't try to replicate a full build system such as [Bazel](https://bazel.build/) or [Pants](https://www.pantsbuild.org/). It just makes it possible to have a simple monorepo with interdependencies.
Una doesn't try to replicate a full build system such as [Bazel](https://bazel.build/) or
[Pants](https://www.pantsbuild.org/).
It just makes it possible to have a simple monorepo with interdependencies.

Una works much like a Rust workspace, with each package having its own pyproject.toml. In general, packages should either be libraries (imported but not run) or apps (run but never imported), but Una will not enforce this.
Una works much like a Rust workspace, with each package having its own pyproject.toml.
In general, packages should either be libraries (imported but not run) or apps (run but never imported), but Una will not enforce this.

Currently it works with the following build backends, but more will follow:

- [Hatch](https://hatch.pypa.io) (used by default and and in all documentation)
- [PDM](https://pdm-project.org/)

All instructions and examples use [uv](https://docs.astral.sh/uv/) for local development.
It only works with [uv](https://docs.astral.sh/uv/) and with the [Hatch](https://hatch.pypa.io) build backend.

## Examples
You can see an example repo here:
Expand Down
2 changes: 1 addition & 1 deletion docs/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ uvx --from build pyproject-build --installer uv apps/printer
```

You'll get some `*.whl` files, which you can then deploy with Docker or whatever you prefer.
They are fully self-contained, so you don't need uv/Hatch/PDM or Una or anything else wherever you want to install them.
They are fully self-contained, so you don't need uv/Hatch or Una or anything else wherever you want to install them.
7 changes: 1 addition & 6 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,7 @@ Una doesn't try to replicate a full build system such as [Bazel](https://bazel.b

Una works much like a Rust workspace, with each package having its own pyproject.toml. In general, packages should either be libraries (imported but not run) or apps (run but never imported), but Una will not enforce this.

Currently it works with the following build backends, but more will follow:

- [Hatch](https://hatch.pypa.io) (used by default and and in all documentation)
- [PDM](https://pdm-project.org/)

All instructions and examples use [uv](https://docs.astral.sh/uv/) for local development.
It only works with [uv](https://docs.astral.sh/uv/) and with the [Hatch](https://hatch.pypa.io) build backend.

## Examples
You can see an example repo here:
Expand Down
23 changes: 14 additions & 9 deletions plugins/hatch/hatch_una/hatch_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from hatchling.builders.config import BuilderConfig
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
from hatchling.builders.sdist import SdistBuilder
from hatchling.plugin import hookimpl

from hatch_una import util
Expand All @@ -21,14 +22,10 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
# load the config for this package
path = Path(self.root)
conf = util.load_conf(path)
name: str = conf["project"]["name"]

try:
int_deps: dict[str, str] = conf["tool"]["una"]["deps"]
except KeyError as e:
raise KeyError(
f"Package '{name}' is missing '[tool.una.deps]' in pyproject.toml"
) from e
members: list[str] = (
conf.get("tool", {}).get("uv", {}).get("workspace", {}).get("members", []) # pyright:ignore[reportAny]
)
_, int_deps = util.get_dependencies(path)

if not int_deps:
# this is fine, the package doesn't import anything internally
Expand All @@ -39,6 +36,14 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
# nothing to do as everything should already be included in sdist...
return

add_dep_files: dict[str, str] = {}
for d in int_deps:
package_dir = util.find_package_dir(d, members)
finder = SdistBuilder(str(package_dir))
files = [Path(f.path) for f in finder.recurse_selected_project_files()]
for f in files:
add_dep_files[str(f)] = str(f.relative_to(package_dir))

# make sure all int_deps exist
found = [Path(k) for k in int_deps if (path / k).exists()]
missing = set(int_deps) - set(str(p) for p in found)
Expand All @@ -53,7 +58,7 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:

build_data["force_include"] = {
**build_data["force_include"],
**int_deps,
**add_dep_files,
**add_packages_pyproj,
}

Expand Down
21 changes: 3 additions & 18 deletions plugins/hatch/hatch_una/hatch_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,7 @@ def update(self, metadata: dict[str, Any]) -> None:

# load the config for this package
path = Path(self.root)
conf = util.load_conf(path)
name: str = conf["project"]["name"]

try:
int_deps: dict[str, str] = conf["tool"]["una"]["deps"]
except KeyError as e:
raise KeyError(
f"Package '{name}' is missing '[tool.una.deps]' in pyproject.toml"
) from e

project_deps: list[str] = metadata.get("dependencies", [])
project_deps = [d.strip().replace(" ", "") for d in project_deps]
ext_deps, int_deps = util.get_dependencies(path)

add_deps: list[str] = []
for dep_path in int_deps:
Expand All @@ -48,15 +37,11 @@ def update(self, metadata: dict[str, Any]) -> None:

# load all third-party dependencies from this internal dependency into the
# project.dependencies table
dep_conf = util.load_conf(use_path)
try:
dep_deps: list[str] = dep_conf["project"]["dependencies"]
except KeyError as e:
raise KeyError(f"No project.dependencies table for '{use_path}'")
dep_deps, _ = util.get_dependencies(use_path)
dep_deps = [d.strip().replace(" ", "") for d in dep_deps]
add_deps.extend(dep_deps)

metadata["dependencies"] = list(set(project_deps + add_deps))
metadata["dependencies"] = list(set(ext_deps + add_deps))


@hookimpl
Expand Down
44 changes: 44 additions & 0 deletions plugins/hatch/hatch_una/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,47 @@
def load_conf(path: Path) -> dict[str, Any]:
with (path / PYPROJ).open("rb") as fp:
return tomllib.load(fp)


def get_dependencies(path: Path) -> tuple[list[str], list[str]]:
conf = load_conf(path)
all_deps: list[str] = conf["project"].get("dependencies", []) # pyright:ignore[reportAny]
try:
sources: dict[str, dict[str, bool]] = conf["tool"]["uv"]["sources"]
except KeyError as e:
raise KeyError(f"No tool.uv.sources table for '{path}'") from e

ext_deps: list[str] = []
int_deps: list[str] = []
for d in all_deps:
if d in sources:
if sources[d]["workspace"]:
int_deps.append(d)
continue
ext_deps.append(d.replace(" ", ""))
return (ext_deps, int_deps)


def find_package_dir(name: str, members: list[str]) -> Path:
root = _get_workspace_root()
for glob in members:
packages = sorted(root.glob(glob))
for p in packages:
if p.name == name:
return p.resolve()
raise ValueError(f"Couldn't find package '{name}'")


def _get_workspace_root() -> Path:
root = _find_upwards(Path.cwd())
if not root:
raise ValueError("Didn't find the workspace root. Expected to find a .git directory.")
return root


def _find_upwards(cwd: Path) -> Path | None:
if cwd == Path(cwd.root) or cwd == cwd.parent:
return None
elif (cwd / ".git").exists():
return cwd
return _find_upwards(cwd.parent)
5 changes: 0 additions & 5 deletions plugins/pdm/README.md

This file was deleted.

Empty file removed plugins/pdm/pdm_una/__init__.py
Empty file.
9 changes: 0 additions & 9 deletions plugins/pdm/pdm_una/hook.py

This file was deleted.

51 changes: 0 additions & 51 deletions plugins/pdm/pdm_una/include.py

This file was deleted.

51 changes: 0 additions & 51 deletions plugins/pdm/pdm_una/meta.py

This file was deleted.

Empty file removed plugins/pdm/pdm_una/py.typed
Empty file.
38 changes: 0 additions & 38 deletions plugins/pdm/pdm_una/util.py

This file was deleted.

Loading