Skip to content

Commit 3c738ed

Browse files
authored
fix: uv workspaces with sourced packages (#234)
1 parent bffaee1 commit 3c738ed

File tree

15 files changed

+142
-3
lines changed

15 files changed

+142
-3
lines changed

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ dynamic = [
4040
]
4141
dependencies = [
4242
"packaging>=24.2",
43+
"tomli>=2.2.1; python_version<'3.11'",
4344
"tox>=4.26,<5",
4445
"typing-extensions>=4.12.2; python_version<'3.10'",
4546
"uv>=0.5.31,<1",
@@ -66,7 +67,7 @@ test = [
6667
"pytest-cov>=6",
6768
"pytest-mock>=3.14",
6869
]
69-
type = [ "mypy==1.15", { include-group = "test" } ]
70+
type = [ "mypy==1.15", "types-setuptools>=80.9.0.20250801", { include-group = "test" } ]
7071
lint = [ "pre-commit-uv>=4.1.4" ]
7172
pkg-meta = [ "check-wheel-contents>=0.6.1", "twine>=6.1", "uv>=0.5.31" ]
7273

src/tox_uv/_installer.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,17 @@
33
from __future__ import annotations
44

55
import logging
6+
import sys
67
from collections import defaultdict
78
from collections.abc import Sequence
9+
from functools import cached_property
810
from itertools import chain
911
from typing import TYPE_CHECKING, Any, Final
1012

13+
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
14+
import tomllib
15+
else: # pragma: no cover (py311+)
16+
import tomli as tomllib
1117
from packaging.requirements import Requirement
1218
from packaging.utils import parse_sdist_filename, parse_wheel_filename
1319
from tox.config.types import Command
@@ -101,6 +107,17 @@ def install(self, arguments: Any, section: str, of_type: str) -> None: # noqa:
101107
_LOGGER.warning("uv cannot install %r", arguments) # pragma: no cover
102108
raise SystemExit(1) # pragma: no cover
103109

110+
@cached_property
111+
def _sourced_pkg_names(self) -> set[str]:
112+
pyproject_file = self._env.conf._conf.src_path.parent / "pyproject.toml" # noqa: SLF001
113+
if not pyproject_file.exists(): # pragma: no cover
114+
return set()
115+
with pyproject_file.open("rb") as file_handler:
116+
pyproject = tomllib.load(file_handler)
117+
118+
sources = pyproject.get("tool", {}).get("uv", {}).get("sources", {})
119+
return {key for key, val in sources.items() if val.get("workspace", False)}
120+
104121
def _install_list_of_deps( # noqa: C901, PLR0912
105122
self,
106123
arguments: Sequence[
@@ -114,12 +131,20 @@ def _install_list_of_deps( # noqa: C901, PLR0912
114131
if isinstance(arg, Requirement): # pragma: no branch
115132
groups["req"].append(str(arg)) # pragma: no cover
116133
elif isinstance(arg, (WheelPackage, SdistPackage, EditablePackage)):
117-
groups["req"].extend(str(i) for i in arg.deps)
134+
for pkg in arg.deps:
135+
if (
136+
isinstance(pkg, Requirement)
137+
and pkg.name in self._sourced_pkg_names
138+
and "." not in groups["uv_editable"]
139+
):
140+
groups["uv_editable"].append(".")
141+
continue
142+
groups["req"].append(str(pkg))
118143
parser = parse_sdist_filename if isinstance(arg, SdistPackage) else parse_wheel_filename
119144
name, *_ = parser(arg.path.name)
120145
groups["pkg"].append(f"{name}@{arg.path}")
121146
elif isinstance(arg, EditableLegacyPackage):
122-
groups["req"].extend(str(i) for i in arg.deps)
147+
groups["req"].extend(str(pkg) for pkg in arg.deps)
123148
groups["dev_pkg"].append(str(arg.path))
124149
elif isinstance(arg, UvPackage):
125150
extras_suffix = f"[{','.join(arg.extras)}]" if arg.extras else ""

tests/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ def demo_pkg_setuptools(root: Path) -> Path:
2828
return root / "demo_pkg_setuptools"
2929

3030

31+
@pytest.fixture(scope="session")
32+
def demo_pkg_workspace(root: Path) -> Path:
33+
return root / "demo_pkg_workspace"
34+
35+
36+
@pytest.fixture(scope="session")
37+
def demo_pkg_no_pyproject(root: Path) -> Path:
38+
return root / "demo_pkg_no_pyproject"
39+
40+
3141
@pytest.fixture(scope="session")
3242
def demo_pkg_inline(root: Path) -> Path:
3343
return root / "demo_pkg_inline"

tests/demo_pkg_no_pyproject/setup.cfg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[metadata]
2+
name=demo-pkg
3+
version=0.0.1
4+
5+
[options]
6+
7+
[bdist_wheel]
8+
universal=1

tests/demo_pkg_no_pyproject/setup.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from __future__ import annotations
2+
3+
from setuptools import setup
4+
5+
setup(name="demo-pkg", package_dir={"": "src"})

tests/demo_pkg_no_pyproject/src/demo_pkg/__init__.py

Whitespace-only changes.

tests/demo_pkg_workspace/README.md

Whitespace-only changes.

tests/demo_pkg_workspace/packages/demo_foo/README.md

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[build-system]
2+
build-backend = "uv_build"
3+
requires = [ "uv-build>=0.8.9,<0.9" ]
4+
5+
[project]
6+
name = "demo-foo"
7+
version = "0.1.0"
8+
description = "Add your description here"
9+
readme = "README.md"
10+
authors = [ { name = "Sorin Sbarnea", email = "[email protected]" } ]
11+
requires-python = ">=3.9"
12+
classifiers = [
13+
"Programming Language :: Python :: 3 :: Only",
14+
"Programming Language :: Python :: 3.9",
15+
"Programming Language :: Python :: 3.10",
16+
"Programming Language :: Python :: 3.11",
17+
"Programming Language :: Python :: 3.12",
18+
"Programming Language :: Python :: 3.13",
19+
]
20+
dependencies = [ "demo-root" ]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from __future__ import annotations
2+
3+
4+
def hello() -> str:
5+
return "Hello from demo-foo!"

0 commit comments

Comments
 (0)