dependence 1.0.5__tar.gz → 1.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dependence-1.0.5 → dependence-1.1.1}/PKG-INFO +77 -12
- {dependence-1.0.5 → dependence-1.1.1}/README.md +76 -11
- {dependence-1.0.5 → dependence-1.1.1}/dependence/__main__.py +4 -1
- {dependence-1.0.5 → dependence-1.1.1}/dependence/_utilities.py +110 -10
- {dependence-1.0.5 → dependence-1.1.1}/dependence/freeze.py +31 -20
- {dependence-1.0.5 → dependence-1.1.1}/dependence/update.py +21 -13
- dependence-1.1.1/dependence/upgrade.py +220 -0
- {dependence-1.0.5 → dependence-1.1.1}/pyproject.toml +13 -7
- {dependence-1.0.5 → dependence-1.1.1}/.gitignore +0 -0
- {dependence-1.0.5 → dependence-1.1.1}/dependence/__init__.py +0 -0
- {dependence-1.0.5 → dependence-1.1.1}/dependence/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dependence
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1.1
|
|
4
4
|
Summary: A dependency management tool for python projects
|
|
5
5
|
Project-URL: Documentation, https://dependence.enorganic.org
|
|
6
6
|
Project-URL: Repository, https://github.com/enorganic/dependence
|
|
@@ -21,11 +21,11 @@ Description-Content-Type: text/markdown
|
|
|
21
21
|
[](https://github.com/enorganic/dependence/actions/workflows/test.yml)
|
|
22
22
|
[](https://badge.fury.io/py/dependence)
|
|
23
23
|
|
|
24
|
-
Dependence provides a Command Line Interface and library for
|
|
25
|
-
a python
|
|
26
|
-
installed in the environment in which `dependence` is
|
|
27
|
-
"freezing" recursively resolved package dependencies
|
|
28
|
-
for a package, instead of the entire environment).
|
|
24
|
+
Dependence provides a Command Line Interface and library for performing
|
|
25
|
+
dependency upgrades on a python project, aligning declared dependencies with
|
|
26
|
+
the package versions installed in the environment in which `dependence` is
|
|
27
|
+
executed, and for "freezing" recursively resolved package dependencies
|
|
28
|
+
(like `pip freeze`, but for a package, instead of the entire environment).
|
|
29
29
|
|
|
30
30
|
- [Documentation](https://enorganic.github.io/dependence/)
|
|
31
31
|
- [Contributing](https://enorganic.github.io/dependence/contributing)
|
|
@@ -38,7 +38,72 @@ You can install `dependence` with pip:
|
|
|
38
38
|
pip3 install dependence
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
##
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
### Upgrading Dependencies
|
|
44
|
+
|
|
45
|
+
The `dependence upgrade` command, and the `dependence.upgrade.upgrade`
|
|
46
|
+
function, discover and upgrade project and environment dependencies in the
|
|
47
|
+
environment in which dependence is installed to their latest version
|
|
48
|
+
aligned with project and dependency requirements, then selectively update
|
|
49
|
+
requirement specifiers in any specified TOML files (such as pyproject.toml),
|
|
50
|
+
setup.cfg file, requirements.txt files, or tox.ini files. Because
|
|
51
|
+
pyproject.toml files may contain dependencies for more than one environment,
|
|
52
|
+
such as when using [hatch](https://hatch.pypa.io/) environments,
|
|
53
|
+
[JSON-style pointers](https://datatracker.ietf.org/doc/html/rfc6901) are used
|
|
54
|
+
to include or exclude specific parts of TOML files.
|
|
55
|
+
|
|
56
|
+
For example, in [this project's Makefile
|
|
57
|
+
](https://github.com/enorganic/dependence/blob/main/Makefile#L27), we define a
|
|
58
|
+
`make upgrade` target as follows:
|
|
59
|
+
|
|
60
|
+
```Makefile
|
|
61
|
+
SHELL := bash
|
|
62
|
+
PYTHON_VERSION := 3.9
|
|
63
|
+
|
|
64
|
+
upgrade:
|
|
65
|
+
hatch run dependence upgrade\
|
|
66
|
+
--include-pointer /tool/hatch/envs/default\
|
|
67
|
+
--include-pointer /project\
|
|
68
|
+
pyproject.toml && \
|
|
69
|
+
hatch run docs:dependence upgrade\
|
|
70
|
+
--include-pointer /tool/hatch/envs/docs\
|
|
71
|
+
--include-pointer /project\
|
|
72
|
+
pyproject.toml && \
|
|
73
|
+
hatch run hatch-static-analysis:dependence upgrade\
|
|
74
|
+
--include-pointer /tool/hatch/envs/docs\
|
|
75
|
+
--include-pointer /project\
|
|
76
|
+
pyproject.toml && \
|
|
77
|
+
hatch run hatch-test.py$(PYTHON_VERSION):dependence upgrade\
|
|
78
|
+
--include-pointer /tool/hatch/envs/hatch-test\
|
|
79
|
+
--include-pointer /project\
|
|
80
|
+
pyproject.toml && \
|
|
81
|
+
make requirements
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You can reference the [associated pyproject.toml file for this project
|
|
85
|
+
](https://github.com/enorganic/dependence/blob/main/pyproject.toml#L21)
|
|
86
|
+
for reference concerning the implications of `--include-pointer`, which
|
|
87
|
+
uses identical syntax to [JSON pointers
|
|
88
|
+
](https://datatracker.ietf.org/doc/html/rfc6901). The `--exclude-pointer`
|
|
89
|
+
parameter works identically, but in reverse. If both `--include-pointer`
|
|
90
|
+
and `--exclude-pointer` are used, only sections which match both conditions
|
|
91
|
+
will be updated.
|
|
92
|
+
|
|
93
|
+
You may refer to the [`dependence upgrade` CLI reference](./cli.md#dependence-upgrade)
|
|
94
|
+
and/or [`dependence.upgrade` API reference](./api/upgrade.md) for details
|
|
95
|
+
concerning this command/module, related options, and more complex use case
|
|
96
|
+
examples.
|
|
97
|
+
|
|
98
|
+
The `dependence upgrade` command, and the `dependence.upgrade.upgrade`
|
|
99
|
+
function, are simply a composite of the dependency listing and update
|
|
100
|
+
functionalities covered below, but which a `pip install --upgrade`
|
|
101
|
+
command executed in between—so please read further for additional details.
|
|
102
|
+
All parameters are directly passed, with the exception of
|
|
103
|
+
`--ignore-update`/`ignore_update`, which is translated to the
|
|
104
|
+
`--ignore`/`ignore` parameter for
|
|
105
|
+
`dependence update`/`dependence.update.update` (renamed in this operation
|
|
106
|
+
for clarity of purpose).
|
|
42
107
|
|
|
43
108
|
### Listing Dependencies
|
|
44
109
|
|
|
@@ -48,8 +113,8 @@ requirements.txt, pyproject.toml, setup.cfg, or tox.ini files. The output
|
|
|
48
113
|
format matches that of `pip freeze`, but only lists dependencies of indicated
|
|
49
114
|
packages and/or editable project locations.
|
|
50
115
|
|
|
51
|
-
You may refer to the [`dependence freeze` CLI reference](
|
|
52
|
-
and/or [`dependence.freeze` API reference](
|
|
116
|
+
You may refer to the [`dependence freeze` CLI reference](./cli.md#dependence-freeze)
|
|
117
|
+
and/or [`dependence.freeze` API reference](./api/freeze.md) for details
|
|
53
118
|
concerning this command/module, related options, and more complex use case
|
|
54
119
|
examples.
|
|
55
120
|
|
|
@@ -157,7 +222,7 @@ $ diff pyproject_before.toml pyproject_after.toml
|
|
|
157
222
|
```
|
|
158
223
|
|
|
159
224
|
As you can see, only the version specifier for tomli changed. We know that
|
|
160
|
-
every dependency was upgraded,
|
|
225
|
+
every dependency was upgraded, so why was only the `tomli` version specifier
|
|
161
226
|
updated? By design. Here are the rules `dependence update` adheres to:
|
|
162
227
|
|
|
163
228
|
- We only update requirements versions when they have *inclusive* specifiers.
|
|
@@ -174,7 +239,7 @@ updated? By design. Here are the rules `dependence update` adheres to:
|
|
|
174
239
|
- If your requirement is unversioned, we don't touch it, of course. This is
|
|
175
240
|
why you didn't see any change for "pip".
|
|
176
241
|
|
|
177
|
-
You may refer to the [`dependence update` CLI reference](
|
|
178
|
-
and/or [`dependence.update` API reference](
|
|
242
|
+
You may refer to the [`dependence update` CLI reference](./cli.md#dependence-update)
|
|
243
|
+
and/or [`dependence.update` API reference](./api/update.md) for details
|
|
179
244
|
concerning this command/module, related options, and more complex use
|
|
180
245
|
cases/examples.
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
[](https://github.com/enorganic/dependence/actions/workflows/test.yml)
|
|
4
4
|
[](https://badge.fury.io/py/dependence)
|
|
5
5
|
|
|
6
|
-
Dependence provides a Command Line Interface and library for
|
|
7
|
-
a python
|
|
8
|
-
installed in the environment in which `dependence` is
|
|
9
|
-
"freezing" recursively resolved package dependencies
|
|
10
|
-
for a package, instead of the entire environment).
|
|
6
|
+
Dependence provides a Command Line Interface and library for performing
|
|
7
|
+
dependency upgrades on a python project, aligning declared dependencies with
|
|
8
|
+
the package versions installed in the environment in which `dependence` is
|
|
9
|
+
executed, and for "freezing" recursively resolved package dependencies
|
|
10
|
+
(like `pip freeze`, but for a package, instead of the entire environment).
|
|
11
11
|
|
|
12
12
|
- [Documentation](https://enorganic.github.io/dependence/)
|
|
13
13
|
- [Contributing](https://enorganic.github.io/dependence/contributing)
|
|
@@ -20,7 +20,72 @@ You can install `dependence` with pip:
|
|
|
20
20
|
pip3 install dependence
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### Upgrading Dependencies
|
|
26
|
+
|
|
27
|
+
The `dependence upgrade` command, and the `dependence.upgrade.upgrade`
|
|
28
|
+
function, discover and upgrade project and environment dependencies in the
|
|
29
|
+
environment in which dependence is installed to their latest version
|
|
30
|
+
aligned with project and dependency requirements, then selectively update
|
|
31
|
+
requirement specifiers in any specified TOML files (such as pyproject.toml),
|
|
32
|
+
setup.cfg file, requirements.txt files, or tox.ini files. Because
|
|
33
|
+
pyproject.toml files may contain dependencies for more than one environment,
|
|
34
|
+
such as when using [hatch](https://hatch.pypa.io/) environments,
|
|
35
|
+
[JSON-style pointers](https://datatracker.ietf.org/doc/html/rfc6901) are used
|
|
36
|
+
to include or exclude specific parts of TOML files.
|
|
37
|
+
|
|
38
|
+
For example, in [this project's Makefile
|
|
39
|
+
](https://github.com/enorganic/dependence/blob/main/Makefile#L27), we define a
|
|
40
|
+
`make upgrade` target as follows:
|
|
41
|
+
|
|
42
|
+
```Makefile
|
|
43
|
+
SHELL := bash
|
|
44
|
+
PYTHON_VERSION := 3.9
|
|
45
|
+
|
|
46
|
+
upgrade:
|
|
47
|
+
hatch run dependence upgrade\
|
|
48
|
+
--include-pointer /tool/hatch/envs/default\
|
|
49
|
+
--include-pointer /project\
|
|
50
|
+
pyproject.toml && \
|
|
51
|
+
hatch run docs:dependence upgrade\
|
|
52
|
+
--include-pointer /tool/hatch/envs/docs\
|
|
53
|
+
--include-pointer /project\
|
|
54
|
+
pyproject.toml && \
|
|
55
|
+
hatch run hatch-static-analysis:dependence upgrade\
|
|
56
|
+
--include-pointer /tool/hatch/envs/docs\
|
|
57
|
+
--include-pointer /project\
|
|
58
|
+
pyproject.toml && \
|
|
59
|
+
hatch run hatch-test.py$(PYTHON_VERSION):dependence upgrade\
|
|
60
|
+
--include-pointer /tool/hatch/envs/hatch-test\
|
|
61
|
+
--include-pointer /project\
|
|
62
|
+
pyproject.toml && \
|
|
63
|
+
make requirements
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
You can reference the [associated pyproject.toml file for this project
|
|
67
|
+
](https://github.com/enorganic/dependence/blob/main/pyproject.toml#L21)
|
|
68
|
+
for reference concerning the implications of `--include-pointer`, which
|
|
69
|
+
uses identical syntax to [JSON pointers
|
|
70
|
+
](https://datatracker.ietf.org/doc/html/rfc6901). The `--exclude-pointer`
|
|
71
|
+
parameter works identically, but in reverse. If both `--include-pointer`
|
|
72
|
+
and `--exclude-pointer` are used, only sections which match both conditions
|
|
73
|
+
will be updated.
|
|
74
|
+
|
|
75
|
+
You may refer to the [`dependence upgrade` CLI reference](./cli.md#dependence-upgrade)
|
|
76
|
+
and/or [`dependence.upgrade` API reference](./api/upgrade.md) for details
|
|
77
|
+
concerning this command/module, related options, and more complex use case
|
|
78
|
+
examples.
|
|
79
|
+
|
|
80
|
+
The `dependence upgrade` command, and the `dependence.upgrade.upgrade`
|
|
81
|
+
function, are simply a composite of the dependency listing and update
|
|
82
|
+
functionalities covered below, but which a `pip install --upgrade`
|
|
83
|
+
command executed in between—so please read further for additional details.
|
|
84
|
+
All parameters are directly passed, with the exception of
|
|
85
|
+
`--ignore-update`/`ignore_update`, which is translated to the
|
|
86
|
+
`--ignore`/`ignore` parameter for
|
|
87
|
+
`dependence update`/`dependence.update.update` (renamed in this operation
|
|
88
|
+
for clarity of purpose).
|
|
24
89
|
|
|
25
90
|
### Listing Dependencies
|
|
26
91
|
|
|
@@ -30,8 +95,8 @@ requirements.txt, pyproject.toml, setup.cfg, or tox.ini files. The output
|
|
|
30
95
|
format matches that of `pip freeze`, but only lists dependencies of indicated
|
|
31
96
|
packages and/or editable project locations.
|
|
32
97
|
|
|
33
|
-
You may refer to the [`dependence freeze` CLI reference](
|
|
34
|
-
and/or [`dependence.freeze` API reference](
|
|
98
|
+
You may refer to the [`dependence freeze` CLI reference](./cli.md#dependence-freeze)
|
|
99
|
+
and/or [`dependence.freeze` API reference](./api/freeze.md) for details
|
|
35
100
|
concerning this command/module, related options, and more complex use case
|
|
36
101
|
examples.
|
|
37
102
|
|
|
@@ -139,7 +204,7 @@ $ diff pyproject_before.toml pyproject_after.toml
|
|
|
139
204
|
```
|
|
140
205
|
|
|
141
206
|
As you can see, only the version specifier for tomli changed. We know that
|
|
142
|
-
every dependency was upgraded,
|
|
207
|
+
every dependency was upgraded, so why was only the `tomli` version specifier
|
|
143
208
|
updated? By design. Here are the rules `dependence update` adheres to:
|
|
144
209
|
|
|
145
210
|
- We only update requirements versions when they have *inclusive* specifiers.
|
|
@@ -156,7 +221,7 @@ updated? By design. Here are the rules `dependence update` adheres to:
|
|
|
156
221
|
- If your requirement is unversioned, we don't touch it, of course. This is
|
|
157
222
|
why you didn't see any change for "pip".
|
|
158
223
|
|
|
159
|
-
You may refer to the [`dependence update` CLI reference](
|
|
160
|
-
and/or [`dependence.update` API reference](
|
|
224
|
+
You may refer to the [`dependence update` CLI reference](./cli.md#dependence-update)
|
|
225
|
+
and/or [`dependence.update` API reference](./api/update.md) for details
|
|
161
226
|
concerning this command/module, related options, and more complex use
|
|
162
227
|
cases/examples.
|
|
@@ -23,7 +23,10 @@ def _print_help() -> None:
|
|
|
23
23
|
"installed\n"
|
|
24
24
|
" distribution or project, in a similar "
|
|
25
25
|
"format\n"
|
|
26
|
-
" to the output of `pip freeze
|
|
26
|
+
" to the output of `pip freeze`.\n"
|
|
27
|
+
" upgrade Upgrade all dependencies and align "
|
|
28
|
+
"project\n"
|
|
29
|
+
" requirement specifiers to match."
|
|
27
30
|
)
|
|
28
31
|
|
|
29
32
|
|
|
@@ -25,6 +25,7 @@ from typing import (
|
|
|
25
25
|
Callable,
|
|
26
26
|
TypedDict,
|
|
27
27
|
cast,
|
|
28
|
+
overload,
|
|
28
29
|
)
|
|
29
30
|
from warnings import warn
|
|
30
31
|
|
|
@@ -37,6 +38,63 @@ _BUILTIN_DISTRIBUTION_NAMES: tuple[str] = ("distribute",)
|
|
|
37
38
|
_UNSAFE_CHARACTERS_PATTERN: re.Pattern = re.compile("[^A-Za-z0-9.]+")
|
|
38
39
|
|
|
39
40
|
|
|
41
|
+
class DefinitionExistsError(Exception):
|
|
42
|
+
"""
|
|
43
|
+
This error is raised when an attempt is made to redefine
|
|
44
|
+
a singleton class instance.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
_module_locals: dict[str, Any] = locals()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Undefined:
|
|
52
|
+
"""
|
|
53
|
+
This class is intended to indicate that a parameter has not been passed
|
|
54
|
+
to a keyword argument in situations where `None` is to be used as a
|
|
55
|
+
meaningful value.
|
|
56
|
+
|
|
57
|
+
The `Undefined` class is a singleton, so only one instance of this class
|
|
58
|
+
is permitted: `sob.UNDEFINED`.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
__module__ = "sob"
|
|
62
|
+
|
|
63
|
+
def __init__(self) -> None:
|
|
64
|
+
# Only one instance of `Undefined` is permitted, so initialization
|
|
65
|
+
# checks to make sure this is the first use.
|
|
66
|
+
if "UNDEFINED" in _module_locals:
|
|
67
|
+
message: str = f"{self!r} may only be instantiated once."
|
|
68
|
+
raise DefinitionExistsError(message)
|
|
69
|
+
|
|
70
|
+
def __repr__(self) -> str:
|
|
71
|
+
# Represent instances of this class using the qualified name for the
|
|
72
|
+
# constant `UNDEFINED`.
|
|
73
|
+
return "sob.UNDEFINED"
|
|
74
|
+
|
|
75
|
+
def __bool__(self) -> bool:
|
|
76
|
+
# `UNDEFINED` cast as a boolean is `False` (as with `None`)
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
def __hash__(self) -> int:
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
def __eq__(self, other: object) -> bool:
|
|
83
|
+
# Another object is only equal to this if it shares the same id, since
|
|
84
|
+
# there should only be one instance of this class defined
|
|
85
|
+
return other is self
|
|
86
|
+
|
|
87
|
+
def __reduce__(self) -> tuple[Callable[[], Undefined], tuple]:
|
|
88
|
+
return _undefined, ()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
UNDEFINED: Undefined = Undefined()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _undefined() -> Undefined:
|
|
95
|
+
return UNDEFINED
|
|
96
|
+
|
|
97
|
+
|
|
40
98
|
def iter_distinct(items: Iterable[Hashable]) -> Iterable:
|
|
41
99
|
"""
|
|
42
100
|
Yield distinct elements, preserving order
|
|
@@ -292,10 +350,14 @@ class ConfigurationFileType(Enum):
|
|
|
292
350
|
|
|
293
351
|
|
|
294
352
|
@functools.lru_cache
|
|
295
|
-
def get_configuration_file_type(
|
|
296
|
-
|
|
353
|
+
def get_configuration_file_type(
|
|
354
|
+
path: str | Path, default: Any = UNDEFINED
|
|
355
|
+
) -> ConfigurationFileType:
|
|
356
|
+
if isinstance(path, str):
|
|
357
|
+
path = Path(path)
|
|
358
|
+
if not path.is_file():
|
|
297
359
|
raise FileNotFoundError(path)
|
|
298
|
-
basename: str =
|
|
360
|
+
basename: str = path.name.lower()
|
|
299
361
|
if basename == "setup.cfg":
|
|
300
362
|
return ConfigurationFileType.SETUP_CFG
|
|
301
363
|
if basename == "tox.ini":
|
|
@@ -306,16 +368,54 @@ def get_configuration_file_type(path: str) -> ConfigurationFileType:
|
|
|
306
368
|
return ConfigurationFileType.REQUIREMENTS_TXT
|
|
307
369
|
if basename.endswith(".toml"):
|
|
308
370
|
return ConfigurationFileType.TOML
|
|
309
|
-
|
|
310
|
-
|
|
371
|
+
if default is UNDEFINED:
|
|
372
|
+
message: str = (
|
|
373
|
+
f"{path!s} is not a recognized type of configuration file."
|
|
374
|
+
)
|
|
375
|
+
raise ValueError(message)
|
|
376
|
+
return default
|
|
311
377
|
|
|
312
378
|
|
|
313
379
|
def is_configuration_file(path: str) -> bool:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
380
|
+
return get_configuration_file_type(path, default=None) is not None
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@overload
|
|
384
|
+
def iter_configuration_files(path: str) -> Iterable[str]: ...
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
@overload
|
|
388
|
+
def iter_configuration_files(path: Path) -> Iterable[Path]: ...
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def iter_configuration_files(path: str | Path) -> Iterable[Path | str]:
|
|
392
|
+
"""
|
|
393
|
+
Iterate over the project configuration files for the given path.
|
|
394
|
+
If the path is a file path—yields only that path. If the path is a
|
|
395
|
+
directory, yields all configuration files in that directory.
|
|
396
|
+
"""
|
|
397
|
+
if os.path.exists(path):
|
|
398
|
+
if os.path.isdir(path):
|
|
399
|
+
child: Path
|
|
400
|
+
for child in filter(
|
|
401
|
+
Path.is_file,
|
|
402
|
+
(
|
|
403
|
+
path.iterdir()
|
|
404
|
+
if isinstance(path, Path)
|
|
405
|
+
else Path(path).iterdir()
|
|
406
|
+
),
|
|
407
|
+
):
|
|
408
|
+
if (
|
|
409
|
+
get_configuration_file_type(child, default=None)
|
|
410
|
+
is not None
|
|
411
|
+
):
|
|
412
|
+
yield (
|
|
413
|
+
child
|
|
414
|
+
if isinstance(path, Path)
|
|
415
|
+
else str(child.absolute())
|
|
416
|
+
)
|
|
417
|
+
elif get_configuration_file_type(path, default=None) is not None:
|
|
418
|
+
yield path
|
|
319
419
|
|
|
320
420
|
|
|
321
421
|
class _EditablePackageMetadata(TypedDict):
|
|
@@ -7,15 +7,16 @@ from functools import partial
|
|
|
7
7
|
from importlib.metadata import Distribution
|
|
8
8
|
from importlib.metadata import distribution as _get_distribution
|
|
9
9
|
from itertools import chain
|
|
10
|
-
from
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import TYPE_CHECKING, cast
|
|
11
12
|
|
|
12
13
|
from dependence._utilities import (
|
|
13
14
|
get_distribution,
|
|
14
15
|
get_required_distribution_names,
|
|
15
16
|
get_requirement_string_distribution_name,
|
|
16
17
|
install_requirement,
|
|
17
|
-
is_configuration_file,
|
|
18
18
|
iter_configuration_file_requirement_strings,
|
|
19
|
+
iter_configuration_files,
|
|
19
20
|
iter_distinct,
|
|
20
21
|
iter_parse_delimited_values,
|
|
21
22
|
normalize_name,
|
|
@@ -74,8 +75,8 @@ def _iter_sort_dependents_last(requirements: Iterable[str]) -> Iterable[str]:
|
|
|
74
75
|
del dependent_dependencies[dependent]
|
|
75
76
|
|
|
76
77
|
|
|
77
|
-
def get_frozen_requirements(
|
|
78
|
-
requirements: Iterable[str] = (),
|
|
78
|
+
def get_frozen_requirements( # noqa: C901
|
|
79
|
+
requirements: Iterable[str | Path] = (),
|
|
79
80
|
*,
|
|
80
81
|
exclude: Iterable[str] = (),
|
|
81
82
|
exclude_recursive: Iterable[str] = (),
|
|
@@ -109,21 +110,30 @@ def get_frozen_requirements(
|
|
|
109
110
|
exclude_pointers: A tuple of JSON pointers indicating elements to
|
|
110
111
|
exclude (defaults to no exclusions). Only applies to TOML files.
|
|
111
112
|
"""
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
requirements = {requirements}
|
|
113
|
+
if isinstance(requirements, (str, Path)):
|
|
114
|
+
requirements = {str(requirements)}
|
|
115
115
|
else:
|
|
116
|
-
requirements = set(requirements)
|
|
116
|
+
requirements = set(map(str, requirements))
|
|
117
117
|
if isinstance(no_version, str):
|
|
118
118
|
no_version = (no_version,)
|
|
119
119
|
elif not isinstance(no_version, tuple):
|
|
120
120
|
no_version = tuple(no_version)
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
# Separate requirement strings from requirement files
|
|
122
|
+
configuration_files: MutableSet[str] = set()
|
|
123
|
+
requirement_strings: MutableSet[str] = set()
|
|
124
|
+
requirement: str | Path
|
|
125
|
+
for requirement in requirements:
|
|
126
|
+
if TYPE_CHECKING:
|
|
127
|
+
assert isinstance(requirement, str)
|
|
128
|
+
requirement_configuration_files: set[str] = set(
|
|
129
|
+
iter_configuration_files(requirement)
|
|
130
|
+
)
|
|
131
|
+
if requirement_configuration_files:
|
|
132
|
+
configuration_files |= requirement_configuration_files
|
|
133
|
+
else:
|
|
134
|
+
if requirement.startswith("setup.py"):
|
|
135
|
+
raise ValueError(requirement)
|
|
136
|
+
requirement_strings.add(requirement)
|
|
127
137
|
frozen_requirements: Iterable[str] = iter_distinct(
|
|
128
138
|
chain(
|
|
129
139
|
requirement_strings,
|
|
@@ -133,10 +143,11 @@ def get_frozen_requirements(
|
|
|
133
143
|
include_pointers=include_pointers,
|
|
134
144
|
exclude_pointers=exclude_pointers,
|
|
135
145
|
),
|
|
136
|
-
|
|
146
|
+
configuration_files,
|
|
137
147
|
),
|
|
138
148
|
)
|
|
139
149
|
)
|
|
150
|
+
frozen_requirements = tuple(frozen_requirements)
|
|
140
151
|
if depth is not None:
|
|
141
152
|
depth -= 1
|
|
142
153
|
if (depth is None) or depth >= 0:
|
|
@@ -242,7 +253,7 @@ def _iter_frozen_requirements(
|
|
|
242
253
|
|
|
243
254
|
|
|
244
255
|
def freeze(
|
|
245
|
-
requirements: Iterable[str] = (),
|
|
256
|
+
requirements: Iterable[str | Path] = (),
|
|
246
257
|
*,
|
|
247
258
|
exclude: Iterable[str] = (),
|
|
248
259
|
exclude_recursive: Iterable[str] = (),
|
|
@@ -262,10 +273,10 @@ def freeze(
|
|
|
262
273
|
"requirement-name[extra-a,extra-b]" or ".[extra-a, extra-b]) and/or
|
|
263
274
|
paths to a setup.py, setup.cfg, pyproject.toml, tox.ini or
|
|
264
275
|
requirements.txt file
|
|
265
|
-
exclude: One or more distributions to exclude
|
|
266
|
-
exclude_recursive: One or more distributions to exclude
|
|
267
|
-
|
|
268
|
-
|
|
276
|
+
exclude: One or more distributions to exclude.
|
|
277
|
+
exclude_recursive: One or more distributions to exclude. Recursive
|
|
278
|
+
dependency discovery is also halted for these distributions,
|
|
279
|
+
unlike those passed to `exclude`.
|
|
269
280
|
no_version: Exclude version numbers from the output
|
|
270
281
|
(only print distribution names) for package names matching any of
|
|
271
282
|
these patterns
|
|
@@ -8,6 +8,7 @@ from copy import deepcopy
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from io import StringIO
|
|
10
10
|
from itertools import chain
|
|
11
|
+
from pathlib import Path
|
|
11
12
|
from typing import (
|
|
12
13
|
IO,
|
|
13
14
|
TYPE_CHECKING,
|
|
@@ -423,12 +424,13 @@ def _get_updated_toml(
|
|
|
423
424
|
|
|
424
425
|
|
|
425
426
|
def _update(
|
|
426
|
-
path: str,
|
|
427
|
+
path: str | Path,
|
|
427
428
|
ignore: Iterable[str] = (),
|
|
428
429
|
all_extra_name: str = "",
|
|
429
430
|
include_pointers: tuple[str, ...] = (),
|
|
430
431
|
exclude_pointers: tuple[str, ...] = (),
|
|
431
432
|
) -> None:
|
|
433
|
+
message: str
|
|
432
434
|
data: str
|
|
433
435
|
update_function: Callable[[str], str]
|
|
434
436
|
kwargs: dict[str, str | Iterable[str]] = {}
|
|
@@ -457,8 +459,8 @@ def _update(
|
|
|
457
459
|
elif configuration_file_type == ConfigurationFileType.REQUIREMENTS_TXT:
|
|
458
460
|
update_function = _get_updated_requirements_txt
|
|
459
461
|
else:
|
|
460
|
-
|
|
461
|
-
raise NotImplementedError(
|
|
462
|
+
message = f"Updating requirements for {path!s} is not supported"
|
|
463
|
+
raise NotImplementedError(message)
|
|
462
464
|
kwargs["ignore"] = ignore
|
|
463
465
|
file_io: IO[str]
|
|
464
466
|
with open(path) as file_io:
|
|
@@ -466,18 +468,19 @@ def _update(
|
|
|
466
468
|
updated_data: str = update_function(data, **kwargs)
|
|
467
469
|
if updated_data == data:
|
|
468
470
|
print( # noqa: T201
|
|
469
|
-
f"All requirements were already up-to-date in {path}"
|
|
471
|
+
f"All requirements were already up-to-date in {path!s}"
|
|
470
472
|
)
|
|
471
473
|
else:
|
|
472
474
|
print( # noqa: T201
|
|
473
|
-
f"Updating requirements in {path}"
|
|
475
|
+
f"Updating requirements in {path!s}"
|
|
474
476
|
)
|
|
475
477
|
with open(path, "w") as file_io:
|
|
476
478
|
file_io.write(updated_data)
|
|
477
479
|
|
|
478
480
|
|
|
479
481
|
def update(
|
|
480
|
-
paths: Iterable[str],
|
|
482
|
+
paths: Iterable[str | Path],
|
|
483
|
+
*,
|
|
481
484
|
ignore: Iterable[str] = (),
|
|
482
485
|
all_extra_name: str = "",
|
|
483
486
|
include_pointers: tuple[str, ...] = (),
|
|
@@ -487,22 +490,27 @@ def update(
|
|
|
487
490
|
Update requirement versions in the specified files.
|
|
488
491
|
|
|
489
492
|
Parameters:
|
|
490
|
-
|
|
493
|
+
paths: One or more local paths to a pyproject.toml,
|
|
491
494
|
setup.cfg, and/or requirements.txt files
|
|
492
|
-
ignore: One or more project names to ignore (leave
|
|
495
|
+
ignore: One or more project/package names to ignore (leave
|
|
496
|
+
as-is) when updating dependency requirement specifiers.
|
|
493
497
|
all_extra_name: If provided, an extra which consolidates
|
|
494
498
|
the requirements for all other extras will be added/updated to
|
|
495
|
-
|
|
499
|
+
pyproject.toml or setup.cfg (this argument is ignored for
|
|
496
500
|
requirements.txt files)
|
|
497
501
|
include_pointers: A tuple of JSON pointers indicating elements to
|
|
498
|
-
include (defaults to all elements).
|
|
502
|
+
include (defaults to all elements). This applies only to TOML
|
|
503
|
+
files (including pyproject.toml), and is ignored for all other
|
|
504
|
+
file types.
|
|
499
505
|
exclude_pointers: A tuple of JSON pointers indicating elements to
|
|
500
|
-
exclude (defaults to no exclusions).
|
|
506
|
+
exclude (defaults to no exclusions). This applies only to TOML
|
|
507
|
+
files (including pyproject.toml), and is ignored for all other
|
|
508
|
+
file types.
|
|
501
509
|
"""
|
|
502
|
-
if isinstance(paths, str):
|
|
510
|
+
if isinstance(paths, (str, Path)):
|
|
503
511
|
paths = (paths,)
|
|
504
512
|
|
|
505
|
-
def update_(path: str) -> None:
|
|
513
|
+
def update_(path: str | Path) -> None:
|
|
506
514
|
_update(
|
|
507
515
|
path,
|
|
508
516
|
ignore=ignore,
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from itertools import chain
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
|
+
|
|
8
|
+
from dependence._utilities import (
|
|
9
|
+
check_output,
|
|
10
|
+
iter_configuration_files,
|
|
11
|
+
iter_parse_delimited_values,
|
|
12
|
+
)
|
|
13
|
+
from dependence.freeze import get_frozen_requirements
|
|
14
|
+
from dependence.update import update
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from collections.abc import Iterable
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def upgrade(
|
|
21
|
+
requirements: Iterable[str],
|
|
22
|
+
*,
|
|
23
|
+
ignore_update: Iterable[str] = (),
|
|
24
|
+
all_extra_name: str = "",
|
|
25
|
+
include_pointers: tuple[str, ...] = (),
|
|
26
|
+
exclude_pointers: tuple[str, ...] = (),
|
|
27
|
+
exclude: Iterable[str] = (),
|
|
28
|
+
exclude_recursive: Iterable[str] = (),
|
|
29
|
+
depth: int | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
"""
|
|
32
|
+
This function obtains a list of dependencies for the specified
|
|
33
|
+
`requirements` using `dependence.update.get_frozen_requirements()`,
|
|
34
|
+
upgrades all of these dependencies in the current environment,
|
|
35
|
+
then updates version specifiers for all requirements/dependencies
|
|
36
|
+
in any or the `requirements` which are project config files
|
|
37
|
+
to align with the newly installed package versions (using
|
|
38
|
+
`dependence.update.update()`).
|
|
39
|
+
|
|
40
|
+
Parameters:
|
|
41
|
+
requirements: One or more requirement specifiers (for example:
|
|
42
|
+
"requirement-name[extra-a,extra-b]" or ".[extra-a, extra-b]) and/or
|
|
43
|
+
paths to a setup.py, setup.cfg, pyproject.toml, tox.ini or
|
|
44
|
+
requirements.txt file.
|
|
45
|
+
ignore_update: One or more project names to ignore (leave as-is)
|
|
46
|
+
when updating a dependency's requirement specifier in the
|
|
47
|
+
provided pyproject.toml/setup.cfg/requirements.txt/tox.ini
|
|
48
|
+
file(s). Note that this does not prevent the package from being
|
|
49
|
+
upgraded—for that you need to pass the package name in
|
|
50
|
+
`exclude` or `exclude_recursive`.
|
|
51
|
+
all_extra_name: If provided, an extra which consolidates
|
|
52
|
+
the requirements for all other extras will be added/updated to
|
|
53
|
+
pyproject.toml or setup.cfg (this argument is ignored for
|
|
54
|
+
requirements.txt and tox.ini files).
|
|
55
|
+
include_pointers: A tuple of JSON pointers indicating elements to
|
|
56
|
+
include (defaults to all elements). This applies only to TOML
|
|
57
|
+
files (including pyproject.toml), and is ignored for all other
|
|
58
|
+
file types.
|
|
59
|
+
exclude_pointers: A tuple of JSON pointers indicating elements to
|
|
60
|
+
exclude (defaults to no exclusions). This applies only to TOML
|
|
61
|
+
files (including pyproject.toml), and is ignored for all other
|
|
62
|
+
file types.
|
|
63
|
+
exclude: One or more distributions to exclude when upgrading packages.
|
|
64
|
+
exclude_recursive: One or more distributions to exclude when
|
|
65
|
+
upgrading packages. Recursive dependency discovery is also
|
|
66
|
+
halted for these distributions, unlike those passed to `exclude`.
|
|
67
|
+
depth: The maximum recursion depth to traverse when discovering
|
|
68
|
+
dependencies. If `None` (the default), all dependencies are
|
|
69
|
+
discovered.
|
|
70
|
+
"""
|
|
71
|
+
frozen_requirements: tuple[str, ...] = get_frozen_requirements(
|
|
72
|
+
requirements,
|
|
73
|
+
exclude=exclude,
|
|
74
|
+
exclude_recursive=exclude_recursive,
|
|
75
|
+
no_version="*",
|
|
76
|
+
depth=depth,
|
|
77
|
+
include_pointers=include_pointers,
|
|
78
|
+
exclude_pointers=exclude_pointers,
|
|
79
|
+
)
|
|
80
|
+
command: tuple[str, ...] = (
|
|
81
|
+
sys.executable,
|
|
82
|
+
"-m",
|
|
83
|
+
"pip",
|
|
84
|
+
"install",
|
|
85
|
+
"--upgrade",
|
|
86
|
+
*frozen_requirements,
|
|
87
|
+
)
|
|
88
|
+
check_output(command)
|
|
89
|
+
configuration_files: tuple[str, ...] = tuple(
|
|
90
|
+
chain(
|
|
91
|
+
*map(iter_configuration_files, requirements) # type: ignore
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
if configuration_files:
|
|
95
|
+
update(
|
|
96
|
+
configuration_files,
|
|
97
|
+
ignore=ignore_update,
|
|
98
|
+
all_extra_name=all_extra_name,
|
|
99
|
+
include_pointers=include_pointers,
|
|
100
|
+
exclude_pointers=exclude_pointers,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main() -> None:
|
|
105
|
+
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
|
106
|
+
prog="dependence upgrade",
|
|
107
|
+
description=(
|
|
108
|
+
"Upgrade all dependencies for specified packages/projects, "
|
|
109
|
+
"then upgrade version specifiers in the project files "
|
|
110
|
+
"to align with newly installed versions of each distribution."
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
parser.add_argument(
|
|
114
|
+
"-iu",
|
|
115
|
+
"--ignore-update",
|
|
116
|
+
default=[],
|
|
117
|
+
type=str,
|
|
118
|
+
action="append",
|
|
119
|
+
help=(
|
|
120
|
+
"A comma-separated list of distributions to ignore (leave "
|
|
121
|
+
"any requirements pertaining to the package as-is) when "
|
|
122
|
+
"updating project files"
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
"-aen",
|
|
127
|
+
"--all-extra-name",
|
|
128
|
+
default="",
|
|
129
|
+
type=str,
|
|
130
|
+
help=(
|
|
131
|
+
"If provided, an extra which consolidates the requirements "
|
|
132
|
+
"for all other extras will be added/updated to pyproject.toml "
|
|
133
|
+
"or setup.cfg (this argument is ignored for "
|
|
134
|
+
"requirements.txt files and other TOML files)"
|
|
135
|
+
),
|
|
136
|
+
)
|
|
137
|
+
parser.add_argument(
|
|
138
|
+
"--include-pointer",
|
|
139
|
+
default=[],
|
|
140
|
+
type=str,
|
|
141
|
+
action="append",
|
|
142
|
+
help=(
|
|
143
|
+
"One or more JSON pointers of elements to *include* "
|
|
144
|
+
"(applies to TOML files only)"
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--exclude-pointer",
|
|
149
|
+
default=[],
|
|
150
|
+
type=str,
|
|
151
|
+
action="append",
|
|
152
|
+
help=(
|
|
153
|
+
"One or more JSON pointers of elements to *exclude* "
|
|
154
|
+
"(applies to TOML files only)"
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
"-e",
|
|
159
|
+
"--exclude",
|
|
160
|
+
default=[],
|
|
161
|
+
type=str,
|
|
162
|
+
action="append",
|
|
163
|
+
help=(
|
|
164
|
+
"A distribution (or comma-separated list of distributions) to "
|
|
165
|
+
"exclude when performing upgrades"
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
"-er",
|
|
170
|
+
"--exclude-recursive",
|
|
171
|
+
default=[],
|
|
172
|
+
type=str,
|
|
173
|
+
action="append",
|
|
174
|
+
help=(
|
|
175
|
+
"A distribution (or comma-separated list of distributions) to "
|
|
176
|
+
"exclude when performing upgrades. Unlike -e / --exclude, "
|
|
177
|
+
"this argument also precludes recursive requirement discovery "
|
|
178
|
+
"for the specified packages, thereby excluding all of the "
|
|
179
|
+
"excluded package's requirements which are not required by "
|
|
180
|
+
"another (non-excluded) distribution from the upgrade."
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
"-d",
|
|
185
|
+
"--depth",
|
|
186
|
+
default=None,
|
|
187
|
+
type=int,
|
|
188
|
+
help="Depth of recursive requirement discovery",
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"requirement",
|
|
192
|
+
nargs="+",
|
|
193
|
+
type=str,
|
|
194
|
+
help=(
|
|
195
|
+
"One or more requirement specifiers (for example: "
|
|
196
|
+
'"requirement-name", "requirement-name[extra-a,extra-b]", '
|
|
197
|
+
'".[extra-a, extra-b]" or '
|
|
198
|
+
'"../other-editable-package-directory[extra-a, extra-b]) '
|
|
199
|
+
"and/or paths to a setup.py, setup.cfg, pyproject.toml, "
|
|
200
|
+
"tox.ini or requirements.txt file"
|
|
201
|
+
),
|
|
202
|
+
)
|
|
203
|
+
namespace: argparse.Namespace = parser.parse_args()
|
|
204
|
+
upgrade(
|
|
205
|
+
requirements=namespace.requirement,
|
|
206
|
+
exclude=tuple(iter_parse_delimited_values(namespace.exclude)),
|
|
207
|
+
exclude_recursive=tuple(
|
|
208
|
+
iter_parse_delimited_values(namespace.exclude_recursive)
|
|
209
|
+
),
|
|
210
|
+
ignore_update=tuple(
|
|
211
|
+
iter_parse_delimited_values(namespace.ignore_update)
|
|
212
|
+
),
|
|
213
|
+
all_extra_name=namespace.all_extra_name,
|
|
214
|
+
include_pointers=tuple(namespace.include_pointer),
|
|
215
|
+
exclude_pointers=tuple(namespace.exclude_pointer),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
if __name__ == "__main__":
|
|
220
|
+
main()
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "dependence"
|
|
9
|
-
version = "1.
|
|
9
|
+
version = "1.1.1"
|
|
10
10
|
description = "A dependency management tool for python projects"
|
|
11
11
|
readme = "README.md"
|
|
12
12
|
license = "MIT"
|
|
@@ -67,7 +67,6 @@ post-install-commands = [
|
|
|
67
67
|
[tool.hatch.envs.default.scripts]
|
|
68
68
|
lint = "ruff check . && ruff format --check . && mypy"
|
|
69
69
|
|
|
70
|
-
|
|
71
70
|
[tool.hatch.envs.docs]
|
|
72
71
|
template = "docs"
|
|
73
72
|
python = "3.13"
|
|
@@ -77,9 +76,15 @@ dependencies = [
|
|
|
77
76
|
"black",
|
|
78
77
|
]
|
|
79
78
|
|
|
79
|
+
[tool.hatch.envs.hatch-static-analysis]
|
|
80
|
+
skip-install = false
|
|
81
|
+
|
|
80
82
|
[tool.hatch.envs.hatch-test]
|
|
81
83
|
template = "hatch-test"
|
|
82
84
|
extra-dependencies = []
|
|
85
|
+
extra-arguments = [
|
|
86
|
+
"--cache-clear",
|
|
87
|
+
]
|
|
83
88
|
|
|
84
89
|
[[tool.hatch.envs.hatch-test.matrix]]
|
|
85
90
|
python = [
|
|
@@ -90,11 +95,9 @@ python = [
|
|
|
90
95
|
"3.13",
|
|
91
96
|
]
|
|
92
97
|
|
|
93
|
-
|
|
94
98
|
[tool.ruff]
|
|
95
99
|
line-length = 79
|
|
96
100
|
|
|
97
|
-
|
|
98
101
|
[tool.ruff.lint]
|
|
99
102
|
ignore = [
|
|
100
103
|
"F842",
|
|
@@ -141,14 +144,17 @@ disallow_untyped_defs = true
|
|
|
141
144
|
disallow_incomplete_defs = true
|
|
142
145
|
|
|
143
146
|
[tool.coverage.run]
|
|
147
|
+
include = [
|
|
148
|
+
"src/**/*.py",
|
|
149
|
+
]
|
|
144
150
|
omit = [
|
|
145
|
-
"
|
|
151
|
+
"src/**/_*.py",
|
|
146
152
|
]
|
|
147
153
|
|
|
148
154
|
[tool.coverage.paths]
|
|
149
155
|
source = [
|
|
150
|
-
"src
|
|
156
|
+
"src/**",
|
|
151
157
|
]
|
|
152
158
|
|
|
153
159
|
[tool.coverage.report]
|
|
154
|
-
fail_under =
|
|
160
|
+
fail_under = 70
|
|
File without changes
|
|
File without changes
|
|
File without changes
|