Skip to content

Commit ed30e36

Browse files
committed
fix: uv workspaces with sourced packages
Fixes: #233
1 parent 809f1be commit ed30e36

File tree

15 files changed

+151
-42
lines changed

15 files changed

+151
-42
lines changed

pyproject.toml

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
[build-system]
22
build-backend = "hatchling.build"
3-
requires = [
4-
"hatch-vcs>=0.4",
5-
"hatchling>=1.27",
6-
]
3+
requires = [ "hatch-vcs>=0.4", "hatchling>=1.27" ]
74

85
[project]
96
name = "tox-uv"
107
description = "Integration of uv with tox."
118
readme = "README.md"
12-
keywords = [
13-
"environments",
14-
"isolated",
15-
"testing",
16-
"virtual",
17-
]
9+
keywords = [ "environments", "isolated", "testing", "virtual" ]
1810
license = "MIT"
19-
maintainers = [
20-
{ name = "Bernát Gábor", email = "[email protected]" },
21-
]
11+
maintainers = [ { name = "Bernát Gábor", email = "[email protected]" } ]
2212
requires-python = ">=3.9"
2313
classifiers = [
2414
"Development Status :: 5 - Production/Stable",
@@ -35,11 +25,10 @@ classifiers = [
3525
"Topic :: Software Development :: Libraries",
3626
"Topic :: System",
3727
]
38-
dynamic = [
39-
"version",
40-
]
28+
dynamic = [ "version" ]
4129
dependencies = [
4230
"packaging>=24.2",
31+
"tomli>=2.2.1; python_version<'3.11'",
4332
"tox>=4.26,<5",
4433
"typing-extensions>=4.12.2; python_version<'3.10'",
4534
"uv>=0.5.31,<1",
@@ -66,16 +55,13 @@ test = [
6655
"pytest-cov>=6",
6756
"pytest-mock>=3.14",
6857
]
69-
type = [ "mypy==1.15", { include-group = "test" } ]
58+
type = [ "mypy==1.15", "types-setuptools", { include-group = "test" } ]
7059
lint = [ "pre-commit-uv>=4.1.4" ]
7160
pkg-meta = [ "check-wheel-contents>=0.6.1", "twine>=6.1", "uv>=0.5.31" ]
7261

7362
[tool.hatch]
7463
build.hooks.vcs.version-file = "src/tox_uv/version.py"
75-
build.targets.sdist.include = [
76-
"/src",
77-
"/tests",
78-
]
64+
build.targets.sdist.include = [ "/src", "/tests" ]
7965
version.source = "vcs"
8066

8167
[tool.ruff]
@@ -85,9 +71,7 @@ unsafe-fixes = true
8571
format.preview = true
8672
format.docstring-code-line-length = 100
8773
format.docstring-code-format = true
88-
lint.select = [
89-
"ALL",
90-
]
74+
lint.select = [ "ALL" ]
9175
lint.ignore = [
9276
"COM812", # Conflict with formatter
9377
"CPY", # No copyright statements
@@ -140,27 +124,14 @@ paths.source = [
140124
"**/src",
141125
"**\\src",
142126
]
143-
paths.other = [
144-
".",
145-
"*/tox_uv",
146-
"*\\tox_uv",
147-
]
148-
report.omit = [
149-
"src/tox_uv/_venv_query.py",
150-
]
127+
paths.other = [ ".", "*/tox_uv", "*\\tox_uv" ]
128+
report.omit = [ "src/tox_uv/_venv_query.py" ]
151129
report.fail_under = 100
152130
run.parallel = true
153-
run.plugins = [
154-
"covdefaults",
155-
]
131+
run.plugins = [ "covdefaults" ]
156132

157133
[tool.mypy]
158134
python_version = "3.12"
159135
show_error_codes = true
160136
strict = true
161-
overrides = [
162-
{ module = [
163-
"virtualenv.*",
164-
"uv.*",
165-
], ignore_missing_imports = true },
166-
]
137+
overrides = [ { module = [ "virtualenv.*", "uv.*" ], ignore_missing_imports = true } ]

src/tox_uv/_installer.py

Lines changed: 26 additions & 1 deletion
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,7 +131,15 @@ 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 i in arg.deps:
135+
if (
136+
isinstance(i, Requirement)
137+
and i.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(i))
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}")

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)