Skip to content

Commit 0ee8853

Browse files
authored
Use PEP-621 to load project dependencies (#2499)
1 parent 252299c commit 0ee8853

File tree

15 files changed

+246
-90
lines changed

15 files changed

+246
-90
lines changed

docs/changelog/2499.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Support PEP-621 static metadata for getting package dependencies - by :user:`gaborbernat`.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ dependencies = [
2828
"platformdirs>=2.5.2",
2929
"pluggy>=1",
3030
"pyproject-api>=0.1.1",
31-
"tomli>=2.0.1",
31+
'tomli>=2.0.1;python_version<"3.11"',
3232
"virtualenv>=20.16.5",
3333
'importlib-metadata>=4.12; python_version < "3.8"',
3434
'typing-extensions>=4.3; python_version < "3.8"',

src/tox/config/source/legacy_toml.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from __future__ import annotations
22

3+
import sys
34
from pathlib import Path
45

5-
import tomli
6+
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
7+
import tomllib
8+
else: # pragma: no cover (py311+)
9+
import tomli as tomllib
10+
611

712
from .ini import IniSource
813

@@ -14,7 +19,7 @@ def __init__(self, path: Path):
1419
if path.name != self.FILENAME or not path.exists():
1520
raise ValueError
1621
with path.open("rb") as file_handler:
17-
toml_content = tomli.load(file_handler)
22+
toml_content = tomllib.load(file_handler)
1823
try:
1924
content = toml_content["tool"]["tox"]["legacy_tox_ini"]
2025
except KeyError:

src/tox/execute/pep517_backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def local_execute(self, options: ExecuteOptions) -> tuple[LocalSubProcessExecute
5959
self.is_alive = True
6060
break
6161
if b"failed to start backend" in status.err:
62-
from tox.tox_env.python.virtual_env.package.pep517 import ToxBackendFailed
62+
from tox.tox_env.python.virtual_env.package.pyproject import ToxBackendFailed
6363

6464
failure = BackendFailed(
6565
result={

src/tox/plugin/manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from tox.session.cmd.run import parallel, sequential
1414
from tox.tox_env import package as package_api
1515
from tox.tox_env.python.virtual_env import runner
16-
from tox.tox_env.python.virtual_env.package import cmd_builder, pep517
16+
from tox.tox_env.python.virtual_env.package import cmd_builder, pyproject
1717
from tox.tox_env.register import REGISTER, ToxEnvRegister
1818

1919
from ..execute import Outcome
@@ -39,7 +39,7 @@ def _register_plugins(self, inline: ModuleType | None) -> None:
3939
loader_api,
4040
provision,
4141
runner,
42-
pep517,
42+
pyproject,
4343
cmd_builder,
4444
legacy,
4545
version_flag,

src/tox/provision.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,12 @@ def tox_add_option(parser: ArgumentParser) -> None:
5858

5959

6060
def provision(state: State) -> int | bool:
61+
# remove the dev and marker to allow local development of the package
6162
state.conf.core.add_config(
6263
keys=["min_version", "minversion"],
6364
of_type=Version,
6465
# do not include local version specifier (because it's not allowed in version spec per PEP-440)
65-
default=Version(current_version.split("+")[0]),
66+
default=Version(current_version),
6667
desc="Define the minimal tox version required to run",
6768
)
6869
state.conf.core.add_config(

src/tox/pytest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ def enable_pep517_backend_coverage() -> Iterator[None]: # noqa: PT004
314314
yield # pragma: no cover
315315
return # pragma: no cover
316316
# the COV_ env variables needs to be passed on for the PEP-517 backend
317-
from tox.tox_env.python.virtual_env.package.pep517 import Pep517VirtualEnvPackager
317+
from tox.tox_env.python.virtual_env.package.pyproject import Pep517VirtualEnvPackager
318318

319319
def default_pass_env(self: Pep517VirtualEnvPackager) -> list[str]:
320320
result = previous(self)

src/tox/session/cmd/run/single.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from tox.execute.api import Outcome, StdinSource
1313
from tox.tox_env.api import ToxEnv
1414
from tox.tox_env.errors import Fail, Skip
15-
from tox.tox_env.python.virtual_env.package.pep517 import ToxBackendFailed
15+
from tox.tox_env.python.virtual_env.package.pyproject import ToxBackendFailed
1616
from tox.tox_env.runner import RunToxEnv
1717

1818
LOGGER = logging.getLogger(__name__)

src/tox/tox_env/python/virtual_env/package/cmd_builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from tox.tox_env.register import ToxEnvRegister
2828
from tox.tox_env.runner import RunToxEnv
2929

30-
from .pep517 import Pep517VirtualEnvPackager
30+
from .pyproject import Pep517VirtualEnvPackager
3131
from .util import dependencies_with_extras
3232

3333
if sys.version_info >= (3, 8): # pragma: no cover (py38+)

src/tox/tox_env/python/virtual_env/package/pep517.py renamed to src/tox/tox_env/python/virtual_env/package/pyproject.py

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@
3030
from importlib.metadata import Distribution, PathDistribution
3131
else: # pragma: no cover (<py38)
3232
from importlib_metadata import Distribution, PathDistribution
33+
34+
if sys.version_info >= (3, 11): # pragma: no cover (py311+)
35+
import tomllib
36+
else: # pragma: no cover (py311+)
37+
import tomli as tomllib
38+
3339
ConfigSettings = Optional[Dict[str, Any]]
3440

3541

@@ -143,23 +149,14 @@ def _teardown(self) -> None:
143149

144150
def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
145151
"""build the package to install"""
152+
deps = self._load_deps(for_env)
146153
of_type: str = for_env["package"]
147-
148-
reqs: list[Requirement] | None = None
149-
if of_type == "wheel":
150-
w_env = self._wheel_build_envs.get(for_env["wheel_build_env"])
151-
if w_env is not None and w_env is not self:
152-
with w_env.display_context(self._has_display_suspended):
153-
reqs = w_env.get_package_dependencies() if isinstance(w_env, Pep517VirtualEnvPackager) else []
154-
if reqs is None:
155-
reqs = self.get_package_dependencies()
156-
157-
extras: set[str] = for_env["extras"]
158-
deps = dependencies_with_extras(reqs, extras)
159154
if of_type == "dev-legacy":
155+
self.setup()
160156
deps = [*self.requires(), *self._frontend.get_requires_for_build_sdist().requires] + deps
161157
package: Package = DevLegacyPackage(self.core["tox_root"], deps) # the folder itself is the package
162158
elif of_type == "sdist":
159+
self.setup()
163160
with self._pkg_lock:
164161
package = SdistPackage(self._frontend.build_sdist(sdist_directory=self.pkg_dir).sdist, deps)
165162
elif of_type == "wheel":
@@ -168,6 +165,7 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
168165
with w_env.display_context(self._has_display_suspended):
169166
return w_env.perform_packaging(for_env)
170167
else:
168+
self.setup()
171169
with self._pkg_lock:
172170
path = self._frontend.build_wheel(
173171
wheel_directory=self.pkg_dir,
@@ -179,6 +177,49 @@ def perform_packaging(self, for_env: EnvConfigSet) -> list[Package]:
179177
raise TypeError(f"cannot handle package type {of_type}") # pragma: no cover
180178
return [package]
181179

180+
def _load_deps(self, for_env: EnvConfigSet) -> list[Requirement]:
181+
# first check if this is statically available via PEP-621
182+
deps = self._load_deps_from_static(for_env)
183+
if deps is None:
184+
deps = self._load_deps_from_built_metadata(for_env)
185+
return deps
186+
187+
def _load_deps_from_static(self, for_env: EnvConfigSet) -> list[Requirement] | None:
188+
pyproject_file = self.core["package_root"] / "pyproject.toml"
189+
if not pyproject_file.exists(): # check if it's static PEP-621 metadata
190+
return None
191+
with pyproject_file.open("rb") as file_handler:
192+
pyproject = tomllib.load(file_handler)
193+
if "project" not in pyproject:
194+
return None # is not a PEP-621 pyproject
195+
project = pyproject["project"]
196+
extras: set[str] = for_env["extras"]
197+
for dynamic in project.get("dynamic", []):
198+
if dynamic == "dependencies" or (extras and dynamic == "optional-dependencies"):
199+
return None # if any dependencies are dynamic we can just calculate all dynamically
200+
201+
deps: list[Requirement] = [Requirement(i) for i in project.get("dependencies", [])]
202+
optional_deps = project.get("optional-dependencies", {})
203+
for extra in extras:
204+
deps.extend(Requirement(i) for i in optional_deps.get(extra, []))
205+
return deps
206+
207+
def _load_deps_from_built_metadata(self, for_env: EnvConfigSet) -> list[Requirement]:
208+
# dependencies might depend on the python environment we're running in => if we build a wheel use that env
209+
# to calculate the package metadata, otherwise ourselves
210+
of_type: str = for_env["package"]
211+
reqs: list[Requirement] | None = None
212+
if of_type == "wheel": # wheel packages
213+
w_env = self._wheel_build_envs.get(for_env["wheel_build_env"])
214+
if w_env is not None and w_env is not self:
215+
with w_env.display_context(self._has_display_suspended):
216+
reqs = w_env.get_package_dependencies() if isinstance(w_env, Pep517VirtualEnvPackager) else []
217+
if reqs is None:
218+
reqs = self.get_package_dependencies()
219+
extras: set[str] = for_env["extras"]
220+
deps = dependencies_with_extras(reqs, extras)
221+
return deps
222+
182223
def get_package_dependencies(self) -> list[Requirement]:
183224
with self._pkg_lock:
184225
if self._package_dependencies is None: # pragma: no branch

0 commit comments

Comments
 (0)