Skip to content

Commit 10c249b

Browse files
committed
Improve --python configuration flexibility.
This is a breaking schema change for `[tool.dev-cmd.python]`, although only in form. No knobs are taken away, some are added, and all can be customized per `--python`. Refer to the details here: https://github.com/jsirois/dev-cmd/blob/main/README.md#custom-pythons
1 parent 3fc28ae commit 10c249b

File tree

9 files changed

+422
-213
lines changed

9 files changed

+422
-213
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ __pycache__/
55
/dist/
66

77
# Tool caches.
8-
.dev-cmd/
98
.mypy_cache*/
109
.pytest_cache/
1110
.ruff_cache/

CHANGES.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release Notes
22

3+
## 0.23.0
4+
5+
This release re-works `--python` venv setup customization with breaking TOML schema changes. Please
6+
review https://github.com/jsirois/dev-cmd/blob/main/README.md#custom-pythons to learn about the new
7+
features and TOML structure changes under the `[tool.dev-cmd] python` key.
8+
39
## 0.22.0
410

511
Add support for per-`--python` venv setup customization. This allows for vagaries in older Pythons

README.md

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -334,8 +334,8 @@ e.g.: a requirement string like `"dev-cmd[old-pythons]"`.
334334

335335
With that done, a minimal configuration looks like so:
336336
```toml
337-
[tool.dev-cmd.python.requirements]
338-
export-command = ["uv", "export", "-q", "--no-emit-project", "-o", "{requirements.txt}"]
337+
[[tool.dev-cmd.python]]
338+
3rdparty-export-command = ["uv", "export", "-q", "--no-emit-project", "-o", "{requirements.txt}"]
339339
```
340340

341341
Here your export command just needs to be able to output a Pip requirements.txt compatible
@@ -346,8 +346,8 @@ By default, `dev-cmd` also installs your project in each custom venv in editable
346346
requirement. You may wish to adjust which extra requirements are installed, in which case you use
347347
the `extra-requirements` key:
348348
```toml
349-
[tool.dev-cmd.python.requirements]
350-
export-command = [
349+
[[tool.dev-cmd.python]]
350+
3rdparty-export-command = [
351351
"uv", "export", "-q",
352352
"--no-emit-project",
353353
"--no-emit-package", "subproject",
@@ -362,33 +362,55 @@ extra-requirements = [
362362
Here we exclude the main project and a local subproject from the requirements export since `uv`
363363
exports hashes for these which Pip does not support for directories. To work around, we just list
364364
these two local projects in `extra-requirements` and they get installed as-is without a hash check
365-
after the exported requirements are installed.
365+
after the exported requirements are installed. You can alternatively supply `extra-requirements` as
366+
a single string, in which case the string will be written out to a file and passed to `pip install`
367+
as a `-r` / `--requirement` file.
368+
369+
You can also supply a `finalize-command` as a list of command line argument strings for the venv.
370+
This command will run last after the 3rdparty requirements and extra requirements are installed and
371+
can use `{venv-python}` and `{venv-site-packages}` placeholders to receive these paths for
372+
manipulating the venv.
366373

367374
You may find the need to vary venv setup per Python `--version`. This is supported by specifying
368-
`extra-requirements` as a list of tables instead of a list of requirement strings. For example:
375+
multiple `[[tool.dev-cmd.python]]` entries. For example:
369376
```toml
370-
[[tool.dev-cmd.python.requirements.extra-requirements]]
377+
[[tool.dev-cmd.python]]
378+
when = "python_version >= '3.7'"
379+
3rdparty-export-command = ["uv", "export", "-q", "--no-emit-project", "-o", "{requirements.txt}"]
380+
381+
[[tool.dev-cmd.python]]
371382
when = "python_version < '3.7'"
372-
pip-req = "pip<23"
373-
install-opts = ["--no-use-pep517"]
374-
reqs = ["-e", "./"]
383+
384+
pip-requirement = "pip<10"
385+
extra-requirements = ["."]
386+
extra-requirements-pip-install-opts = ["--no-use-pep517"]
375387
```
376388

377-
You must ensure just one `extra-requirements` entry is selected per `--python` via a `when`
378-
environment marker. You can then customise the version of Pip selected for the venv via `pip-req`,
379-
the extra `reqs` to install and any custom `pip install` options you need.
389+
You must ensure just one `[[tool.dev-cmd.python]]` entry is selected per `--python` via a `when`
390+
environment marker. You can then customize the version of Pip selected for the venv via
391+
`pip-requirement`, the extra `extra-requirements` to install and any custom `pip install` options
392+
you need for either the 3rdparty requirements install via `3rdparty-pip-install-opts` or the extra
393+
requirements install via `extra-requirements-pip-install-opts`.
394+
395+
Note that when defining multiple `[[tool.dev-cmd.python]]` entries, the 1st is special in setting
396+
defaults all subsequent `[[tool.dev-cmd.python]]` entries inherit for keys left unspecified. In the
397+
example above, the second entry for Python 3.6 and older could add a `3rdparty-export-command` if
398+
it needed different export behavior for those older versions.
380399

381400
Venvs are created under a `.dev-cmd` directory and are cached based on the values of the
382401
"build-system", "project" and "project.optional-dependencies" in `pyproject.toml` by default. To
383-
change the default input keys, you can specify `input-keys`. You can also mix the full contents of
384-
any other files into the venv cache key using `input-files`. Here, combining both of these options,
385-
we turn off pyproject.toml inputs to the venv cache key and just rely on the contents of `uv.lock`,
386-
which is what the export command is powered by:
402+
change this default, you can specify a custom `pyproject-cache-keys`. You can also mix the full
403+
contents of any other files, directories or environment variables into the venv cache key using
404+
`extra-cache-keys`. For files or directories, add a string entry denoting their path or else an
405+
entry like `{path = "the/path"}`. For environment variables, add an entry like
406+
`{env = "THE_ENV_VAR"}`. Here, combining both of these options, we turn off pyproject.toml inputs to
407+
the venv cache key and just rely on the contents of `uv.lock`, which is what the export command is
408+
powered by:
387409
```toml
388410
[tool.dev-cmd.python.requirements]
389411
export-command = ["uv", "export", "-q", "--no-emit-project", "-o", "{requirements.txt}"]
390-
input-keys = []
391-
input-files = ["uv.lock"]
412+
pyproject-cache-keys = []
413+
extra-cache-keys = ["uv.lock"]
392414
```
393415

394416
## Execution

dev_cmd/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# Copyright 2024 John Sirois.
22
# Licensed under the Apache License, Version 2.0 (see LICENSE).
33

4-
__version__ = "0.22.0"
4+
__version__ = "0.23.0"

dev_cmd/model.py

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from dataclasses import dataclass
88
from enum import Enum
99
from pathlib import PurePath
10-
from typing import Any, Container, Iterable, Mapping, MutableMapping
10+
from typing import Any, Container, Mapping, MutableMapping
1111

1212
from packaging.markers import Marker
1313

@@ -75,40 +75,33 @@ def __str__(self) -> str:
7575

7676

7777
@dataclass(frozen=True)
78-
class ExtraRequirements:
79-
@classmethod
80-
def create(
81-
cls,
82-
reqs: Iterable[str] | None = None,
83-
pip_req: str | None = None,
84-
install_opts: Iterable[str] | None = None,
85-
) -> ExtraRequirements:
86-
return cls(
87-
reqs=tuple(reqs or ["-e", "."]),
88-
pip_req=pip_req or "pip",
89-
install_opts=tuple(install_opts) if install_opts else (),
90-
)
91-
92-
reqs: tuple[str, ...]
93-
pip_req: str
94-
install_opts: tuple[str, ...]
78+
class CacheKeyInputs:
79+
pyproject_data: Mapping[str, Any]
80+
envs: Mapping[str, str | None]
81+
paths: tuple[str, ...]
9582

9683

9784
@dataclass(frozen=True)
9885
class PythonConfig:
99-
input_data: bytes
100-
input_files: tuple[str, ...]
101-
requirements_export_command: tuple[str, ...]
102-
extra_requirements: ExtraRequirements
86+
cache_key_inputs: CacheKeyInputs
87+
thirdparty_export_command: Command
88+
thirdparty_pip_install_opts: tuple[str, ...]
89+
pip_requirement: str
90+
extra_requirements: tuple[str, ...] | str
91+
extra_requirements_pip_install_opts: tuple[str, ...]
92+
finalize_command: Command | None
10393

10494

10595
@dataclass(frozen=True)
10696
class Venv:
10797
dir: str
10898
python: str
109-
bin_path: str
11099
marker_environment: Mapping[str, str]
111100

101+
@property
102+
def bin_path(self) -> str:
103+
return os.path.dirname(self.python)
104+
112105
def update_path(self, env: MutableMapping[str, str]) -> None:
113106
path = env.pop("PATH", None)
114107
env["PATH"] = (self.bin_path + os.pathsep + path) if path else self.bin_path

0 commit comments

Comments
 (0)