winipedia-utils 0.2.63__py3-none-any.whl → 0.6.6__py3-none-any.whl
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.
Potentially problematic release.
This version of winipedia-utils might be problematic. Click here for more details.
- winipedia_utils/artifacts/build.py +78 -0
- winipedia_utils/concurrent/concurrent.py +7 -2
- winipedia_utils/concurrent/multiprocessing.py +1 -2
- winipedia_utils/concurrent/multithreading.py +2 -2
- winipedia_utils/data/dataframe/cleaning.py +337 -100
- winipedia_utils/git/github/__init__.py +1 -0
- winipedia_utils/git/github/github.py +31 -0
- winipedia_utils/git/github/repo/__init__.py +1 -0
- winipedia_utils/git/github/repo/protect.py +103 -0
- winipedia_utils/git/github/repo/repo.py +205 -0
- winipedia_utils/git/github/workflows/base/__init__.py +1 -0
- winipedia_utils/git/github/workflows/base/base.py +889 -0
- winipedia_utils/git/github/workflows/health_check.py +69 -0
- winipedia_utils/git/github/workflows/publish.py +51 -0
- winipedia_utils/git/github/workflows/release.py +90 -0
- winipedia_utils/git/gitignore/config.py +77 -0
- winipedia_utils/git/gitignore/gitignore.py +5 -63
- winipedia_utils/git/pre_commit/config.py +49 -59
- winipedia_utils/git/pre_commit/hooks.py +46 -46
- winipedia_utils/git/pre_commit/run_hooks.py +19 -12
- winipedia_utils/iterating/iterate.py +63 -1
- winipedia_utils/modules/class_.py +69 -12
- winipedia_utils/modules/function.py +26 -3
- winipedia_utils/modules/inspection.py +56 -0
- winipedia_utils/modules/module.py +22 -28
- winipedia_utils/modules/package.py +116 -10
- winipedia_utils/projects/poetry/config.py +255 -112
- winipedia_utils/projects/poetry/poetry.py +230 -13
- winipedia_utils/projects/project.py +11 -42
- winipedia_utils/setup.py +11 -29
- winipedia_utils/testing/config.py +127 -0
- winipedia_utils/testing/create_tests.py +5 -19
- winipedia_utils/testing/skip.py +19 -0
- winipedia_utils/testing/tests/base/fixtures/fixture.py +36 -0
- winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +3 -3
- winipedia_utils/testing/tests/base/fixtures/scopes/module.py +9 -6
- winipedia_utils/testing/tests/base/fixtures/scopes/session.py +27 -176
- winipedia_utils/testing/tests/base/utils/utils.py +27 -57
- winipedia_utils/text/config.py +250 -0
- winipedia_utils/text/string.py +30 -0
- winipedia_utils-0.6.6.dist-info/METADATA +390 -0
- {winipedia_utils-0.2.63.dist-info → winipedia_utils-0.6.6.dist-info}/RECORD +46 -34
- winipedia_utils/consts.py +0 -21
- winipedia_utils/git/workflows/base/base.py +0 -77
- winipedia_utils/git/workflows/publish.py +0 -79
- winipedia_utils/git/workflows/release.py +0 -91
- winipedia_utils-0.2.63.dist-info/METADATA +0 -738
- /winipedia_utils/{git/workflows/base → artifacts}/__init__.py +0 -0
- /winipedia_utils/git/{workflows → github/workflows}/__init__.py +0 -0
- {winipedia_utils-0.2.63.dist-info → winipedia_utils-0.6.6.dist-info}/WHEEL +0 -0
- {winipedia_utils-0.2.63.dist-info → winipedia_utils-0.6.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,117 +1,260 @@
|
|
|
1
1
|
"""Config utilities for poetry and pyproject.toml."""
|
|
2
2
|
|
|
3
|
+
from functools import cache
|
|
3
4
|
from pathlib import Path
|
|
4
|
-
from typing import Any
|
|
5
|
-
|
|
6
|
-
import
|
|
7
|
-
from
|
|
8
|
-
|
|
9
|
-
from winipedia_utils.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
5
|
+
from typing import Any, cast
|
|
6
|
+
|
|
7
|
+
import requests
|
|
8
|
+
from packaging.version import Version
|
|
9
|
+
|
|
10
|
+
from winipedia_utils.modules.package import get_src_package
|
|
11
|
+
from winipedia_utils.projects.poetry.poetry import VersionConstraint
|
|
12
|
+
from winipedia_utils.testing.config import ExperimentConfigFile
|
|
13
|
+
from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
|
|
14
|
+
from winipedia_utils.text.config import ConfigFile, TomlConfigFile
|
|
15
|
+
from winipedia_utils.text.string import make_name_from_obj
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PyprojectConfigFile(TomlConfigFile):
|
|
19
|
+
"""Config file for pyproject.toml."""
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def get_parent_path(cls) -> Path:
|
|
23
|
+
"""Get the path to the config file."""
|
|
24
|
+
return Path()
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_configs(cls) -> dict[str, Any]:
|
|
28
|
+
"""Get the config."""
|
|
29
|
+
return {
|
|
30
|
+
"project": {
|
|
31
|
+
"name": make_name_from_obj(get_src_package(), capitalize=False),
|
|
32
|
+
"readme": "README.md",
|
|
33
|
+
"dynamic": ["dependencies"],
|
|
34
|
+
},
|
|
35
|
+
"build-system": {
|
|
36
|
+
"requires": ["poetry-core>=2.0.0,<3.0.0"],
|
|
37
|
+
"build-backend": "poetry.core.masonry.api",
|
|
38
|
+
},
|
|
39
|
+
"tool": {
|
|
40
|
+
"poetry": {
|
|
41
|
+
"packages": [{"include": get_src_package().__name__}],
|
|
42
|
+
"dependencies": dict.fromkeys(
|
|
43
|
+
cls.get_dependencies(),
|
|
44
|
+
"*",
|
|
45
|
+
),
|
|
46
|
+
"group": {
|
|
47
|
+
"dev": {
|
|
48
|
+
"dependencies": dict.fromkeys(
|
|
49
|
+
cls.get_dev_dependencies(),
|
|
50
|
+
"*",
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
"ruff": {
|
|
56
|
+
"exclude": [".*", "**/migrations/*.py"],
|
|
57
|
+
"lint": {
|
|
58
|
+
"select": ["ALL"],
|
|
59
|
+
"ignore": ["D203", "D213", "COM812", "ANN401"],
|
|
60
|
+
"fixable": ["ALL"],
|
|
61
|
+
"pydocstyle": {"convention": "google"},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"mypy": {
|
|
65
|
+
"strict": True,
|
|
66
|
+
"warn_unreachable": True,
|
|
67
|
+
"show_error_codes": True,
|
|
68
|
+
"files": ".",
|
|
69
|
+
},
|
|
70
|
+
"pytest": {"ini_options": {"testpaths": [TESTS_PACKAGE_NAME]}},
|
|
71
|
+
"bandit": {
|
|
72
|
+
"exclude_dirs": [ExperimentConfigFile.get_path().as_posix()],
|
|
59
73
|
},
|
|
60
74
|
},
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def get_package_name(cls) -> str:
|
|
79
|
+
"""Get the package name."""
|
|
80
|
+
project_dict = cls.load().get("project", {})
|
|
81
|
+
package_name = str(project_dict.get("name", ""))
|
|
82
|
+
return package_name.replace("-", "_")
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def get_all_dependencies(cls) -> set[str]:
|
|
86
|
+
"""Get all dependencies."""
|
|
87
|
+
return cls.get_dependencies() | cls.get_dev_dependencies()
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def get_dev_dependencies(cls) -> set[str]:
|
|
91
|
+
"""Get the dev dependencies."""
|
|
92
|
+
dev_dependencies = set(
|
|
93
|
+
cls.load()
|
|
94
|
+
.get("tool", {})
|
|
95
|
+
.get("poetry", {})
|
|
96
|
+
.get("group", {})
|
|
97
|
+
.get("dev", {})
|
|
98
|
+
.get("dependencies", {})
|
|
99
|
+
.keys()
|
|
100
|
+
)
|
|
101
|
+
if not dev_dependencies:
|
|
102
|
+
dev_dependencies = set(
|
|
103
|
+
cls.load().get("dependency-groups", {}).get("dev", [])
|
|
104
|
+
)
|
|
105
|
+
dev_dependencies = {d.split("(")[0].strip() for d in dev_dependencies}
|
|
106
|
+
return dev_dependencies
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def get_dependencies(cls) -> set[str]:
|
|
110
|
+
"""Get the dependencies."""
|
|
111
|
+
return set(
|
|
112
|
+
cls.load().get("tool", {}).get("poetry", {}).get("dependencies", {}).keys()
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def get_expected_dev_dependencies(cls) -> set[str]:
|
|
117
|
+
"""Get the expected dev dependencies."""
|
|
118
|
+
return set(
|
|
119
|
+
cls.get_configs()["tool"]["poetry"]["group"]["dev"]["dependencies"].keys()
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def get_authors(cls) -> list[dict[str, str]]:
|
|
124
|
+
"""Get the authors."""
|
|
125
|
+
return cast(
|
|
126
|
+
"list[dict[str, str]]", cls.load().get("project", {}).get("authors", [])
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
@classmethod
|
|
130
|
+
def get_main_author(cls) -> dict[str, str]:
|
|
131
|
+
"""Get the main author.
|
|
132
|
+
|
|
133
|
+
Assumes the main author is the first author.
|
|
134
|
+
"""
|
|
135
|
+
return cls.get_authors()[0]
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def get_main_author_name(cls) -> str:
|
|
139
|
+
"""Get the main author name."""
|
|
140
|
+
return cls.get_main_author()["name"]
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
@cache
|
|
144
|
+
def fetch_latest_python_version(cls) -> Version:
|
|
145
|
+
"""Fetch the latest python version from python.org."""
|
|
146
|
+
url = "https://endoflife.date/api/python.json"
|
|
147
|
+
resp = requests.get(url, timeout=10)
|
|
148
|
+
resp.raise_for_status()
|
|
149
|
+
data = resp.json()
|
|
150
|
+
# first element has metadata for latest stable
|
|
151
|
+
latest_version = data[0]["latest"]
|
|
152
|
+
return Version(latest_version)
|
|
153
|
+
|
|
154
|
+
@classmethod
|
|
155
|
+
def get_latest_possible_python_version(cls) -> Version:
|
|
156
|
+
"""Get the latest possible python version."""
|
|
157
|
+
constraint = cls.load()["project"]["requires-python"]
|
|
158
|
+
version_constraint = VersionConstraint(constraint)
|
|
159
|
+
version = version_constraint.get_upper_inclusive()
|
|
160
|
+
if version is None:
|
|
161
|
+
version = cls.fetch_latest_python_version()
|
|
162
|
+
return version
|
|
163
|
+
|
|
164
|
+
@classmethod
|
|
165
|
+
def get_first_supported_python_version(cls) -> Version:
|
|
166
|
+
"""Get the first supported python version."""
|
|
167
|
+
constraint = cls.load()["project"]["requires-python"]
|
|
168
|
+
version_constraint = VersionConstraint(constraint)
|
|
169
|
+
lower = version_constraint.get_lower_inclusive()
|
|
170
|
+
if lower is None:
|
|
171
|
+
msg = "Need a lower bound for python version"
|
|
172
|
+
raise ValueError(msg)
|
|
173
|
+
return lower
|
|
174
|
+
|
|
175
|
+
@classmethod
|
|
176
|
+
def get_supported_python_versions(cls) -> list[Version]:
|
|
177
|
+
"""Get all supported python versions."""
|
|
178
|
+
constraint = cls.load()["project"]["requires-python"]
|
|
179
|
+
version_constraint = VersionConstraint(constraint)
|
|
180
|
+
return version_constraint.get_version_range(
|
|
181
|
+
level="minor", upper_default=cls.fetch_latest_python_version()
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class TypedConfigFile(ConfigFile):
|
|
186
|
+
"""Config file for py.typed."""
|
|
187
|
+
|
|
188
|
+
@classmethod
|
|
189
|
+
def get_file_extension(cls) -> str:
|
|
190
|
+
"""Get the file extension of the config file."""
|
|
191
|
+
return "typed"
|
|
192
|
+
|
|
193
|
+
@classmethod
|
|
194
|
+
def load(cls) -> dict[str, Any] | list[Any]:
|
|
195
|
+
"""Load the config file."""
|
|
196
|
+
return {}
|
|
197
|
+
|
|
198
|
+
@classmethod
|
|
199
|
+
def dump(cls, config: dict[str, Any] | list[Any]) -> None:
|
|
200
|
+
"""Dump the config file."""
|
|
201
|
+
if config:
|
|
202
|
+
msg = "Cannot dump to py.typed file."
|
|
203
|
+
raise ValueError(msg)
|
|
204
|
+
|
|
205
|
+
@classmethod
|
|
206
|
+
def get_configs(cls) -> dict[str, Any] | list[Any]:
|
|
207
|
+
"""Get the config."""
|
|
208
|
+
return {}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class PyTypedConfigFile(ConfigFile):
|
|
212
|
+
"""Config file for py.typed."""
|
|
213
|
+
|
|
214
|
+
@classmethod
|
|
215
|
+
def get_parent_path(cls) -> Path:
|
|
216
|
+
"""Get the path to the config file."""
|
|
217
|
+
return Path(PyprojectConfigFile.get_package_name())
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class DotPythonVersionConfigFile(ConfigFile):
|
|
221
|
+
"""Config file for .python-version."""
|
|
222
|
+
|
|
223
|
+
VERSION_KEY = "version"
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def get_filename(cls) -> str:
|
|
227
|
+
"""Get the filename of the config file."""
|
|
228
|
+
return "" # so it builds the path .python-version
|
|
229
|
+
|
|
230
|
+
@classmethod
|
|
231
|
+
def get_file_extension(cls) -> str:
|
|
232
|
+
"""Get the file extension of the config file."""
|
|
233
|
+
return "python-version"
|
|
234
|
+
|
|
235
|
+
@classmethod
|
|
236
|
+
def get_parent_path(cls) -> Path:
|
|
237
|
+
"""Get the path to the config file."""
|
|
238
|
+
return Path()
|
|
239
|
+
|
|
240
|
+
@classmethod
|
|
241
|
+
def get_configs(cls) -> dict[str, Any]:
|
|
242
|
+
"""Get the config."""
|
|
243
|
+
return {
|
|
244
|
+
cls.VERSION_KEY: str(
|
|
245
|
+
PyprojectConfigFile.get_first_supported_python_version()
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def load(cls) -> dict[str, Any]:
|
|
251
|
+
"""Load the config file."""
|
|
252
|
+
return {cls.VERSION_KEY: cls.get_path().read_text()}
|
|
253
|
+
|
|
254
|
+
@classmethod
|
|
255
|
+
def dump(cls, config: dict[str, Any] | list[Any]) -> None:
|
|
256
|
+
"""Dump the config file."""
|
|
257
|
+
if not isinstance(config, dict):
|
|
258
|
+
msg = f"Cannot dump {config} to .python-version file."
|
|
259
|
+
raise TypeError(msg)
|
|
260
|
+
cls.get_path().write_text(config[cls.VERSION_KEY])
|
|
@@ -3,29 +3,246 @@
|
|
|
3
3
|
This module provides utility functions for working with Python projects
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from types import ModuleType
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
from packaging.specifiers import SpecifierSet
|
|
11
|
+
from packaging.version import Version
|
|
7
12
|
|
|
8
|
-
from winipedia_utils.consts import _DEV_DEPENDENCIES
|
|
9
13
|
from winipedia_utils.logging.logger import get_logger
|
|
10
|
-
from winipedia_utils.os.os import run_subprocess, which_with_raise
|
|
11
14
|
|
|
12
15
|
logger = get_logger(__name__)
|
|
13
16
|
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
POETRY_ARG = "poetry"
|
|
19
|
+
|
|
20
|
+
POETRY_RUN_ARGS = [POETRY_ARG, "run"]
|
|
21
|
+
|
|
22
|
+
RUN_PYTHON_MODULE_ARGS = ["python", "-m"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_script_from_args(args: Iterable[str]) -> str:
|
|
26
|
+
"""Get the script from args."""
|
|
27
|
+
return " ".join(args)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_run_python_module_args(module: ModuleType) -> list[str]:
|
|
31
|
+
"""Get the args to run a module."""
|
|
32
|
+
from winipedia_utils.modules.module import ( # noqa: PLC0415 # avoid circular import
|
|
33
|
+
make_obj_importpath,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return [*RUN_PYTHON_MODULE_ARGS, make_obj_importpath(module)]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_poetry_run_module_args(module: ModuleType) -> list[str]:
|
|
40
|
+
"""Get the args to run a module."""
|
|
41
|
+
return [*POETRY_RUN_ARGS, *get_run_python_module_args(module)]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_python_module_script(module: ModuleType) -> str:
|
|
45
|
+
"""Get the script to run a module."""
|
|
46
|
+
return get_script_from_args(get_run_python_module_args(module))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def get_poetry_run_module_script(module: ModuleType) -> str:
|
|
50
|
+
"""Get the script to run a module."""
|
|
51
|
+
return get_script_from_args(get_poetry_run_module_args(module))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class VersionConstraint:
|
|
55
|
+
"""Version class."""
|
|
56
|
+
|
|
57
|
+
def __init__(self, constraint: str) -> None:
|
|
58
|
+
"""Initialize the version."""
|
|
59
|
+
self.constraint = constraint
|
|
60
|
+
self.spec = self.constraint.strip().strip('"').strip("'")
|
|
61
|
+
self.sset = SpecifierSet(self.spec)
|
|
62
|
+
|
|
63
|
+
self.lowers_inclusive = [
|
|
64
|
+
Version(s.version) for s in self.sset if s.operator == ">="
|
|
65
|
+
]
|
|
66
|
+
self.lowers_exclusive = [
|
|
67
|
+
Version(s.version) for s in self.sset if s.operator == ">"
|
|
68
|
+
]
|
|
69
|
+
# increment the last number of exclusive, so
|
|
70
|
+
# >3.4.1 to >=3.4.2; <3.4.0 to <=3.4.1; 3.0.0 to <=3.0.1
|
|
71
|
+
self.lowers_exclusive_to_inclusive = [
|
|
72
|
+
Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.lowers_exclusive
|
|
73
|
+
]
|
|
74
|
+
self.lowers_inclusive = (
|
|
75
|
+
self.lowers_inclusive + self.lowers_exclusive_to_inclusive
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
self.uppers_inclusive = [
|
|
79
|
+
Version(s.version) for s in self.sset if s.operator == "<="
|
|
80
|
+
]
|
|
81
|
+
self.uppers_exclusive = [
|
|
82
|
+
Version(s.version) for s in self.sset if s.operator == "<"
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
# increment the last number of inclusive, so
|
|
86
|
+
# <=3.4.1 to <3.4.2; >=3.4.0 to >3.4.1; 3.0.0 to >3.0.1
|
|
87
|
+
self.uppers_inclusive_to_exclusive = [
|
|
88
|
+
Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.uppers_inclusive
|
|
89
|
+
]
|
|
90
|
+
self.uppers_exclusive = (
|
|
91
|
+
self.uppers_inclusive_to_exclusive + self.uppers_exclusive
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
self.upper_exclusive = (
|
|
95
|
+
min(self.uppers_exclusive) if self.uppers_exclusive else None
|
|
96
|
+
)
|
|
97
|
+
self.lower_inclusive = (
|
|
98
|
+
max(self.lowers_inclusive) if self.lowers_inclusive else None
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def get_lower_inclusive(
|
|
102
|
+
self, default: str | Version | None = None
|
|
103
|
+
) -> Version | None:
|
|
104
|
+
"""Get the minimum version.
|
|
105
|
+
|
|
106
|
+
Is given inclusive. E.g. >=3.8, <3.12 -> 3.8
|
|
107
|
+
if >3.7, <3.12 -> 3.7.1
|
|
108
|
+
|
|
109
|
+
E.g. >=3.8, <3.12 -> 3.8
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
default: The default value to return if there is no minimum version
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
The minimum version
|
|
116
|
+
"""
|
|
117
|
+
default = str(default) if default else None
|
|
118
|
+
if self.lower_inclusive is None:
|
|
119
|
+
return Version(default) if default else None
|
|
120
|
+
|
|
121
|
+
return self.lower_inclusive
|
|
122
|
+
|
|
123
|
+
def get_upper_exclusive(
|
|
124
|
+
self, default: str | Version | None = None
|
|
125
|
+
) -> Version | None:
|
|
126
|
+
"""Get the maximum version.
|
|
127
|
+
|
|
128
|
+
Is given exclusive. E.g. >=3.8, <3.12 -> 3.12
|
|
129
|
+
if >=3.8, <=3.12 -> 3.12.1
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
default: The default value to return if there is no maximum version
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The maximum version
|
|
136
|
+
"""
|
|
137
|
+
default = str(default) if default else None
|
|
138
|
+
if self.upper_exclusive is None:
|
|
139
|
+
return Version(default) if default else None
|
|
140
|
+
|
|
141
|
+
return self.upper_exclusive
|
|
142
|
+
|
|
143
|
+
def get_upper_inclusive(
|
|
144
|
+
self, default: str | Version | None = None
|
|
145
|
+
) -> Version | None:
|
|
146
|
+
"""Get the maximum version.
|
|
147
|
+
|
|
148
|
+
Is given inclusive. E.g. >=3.8, <3.12 -> 3.11
|
|
149
|
+
if >=3.8, <=3.12 -> 3.12
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
default: The default value to return if there is no maximum version
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
The maximum version
|
|
156
|
+
"""
|
|
157
|
+
# increment the default by 1 micro to make it exclusive
|
|
158
|
+
if default:
|
|
159
|
+
default = Version(str(default))
|
|
160
|
+
default = Version(f"{default.major}.{default.minor}.{default.micro + 1}")
|
|
161
|
+
upper_exclusive = self.get_upper_exclusive(default)
|
|
162
|
+
if upper_exclusive is None:
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if upper_exclusive.micro != 0:
|
|
166
|
+
return Version(
|
|
167
|
+
f"{upper_exclusive.major}.{upper_exclusive.minor}.{upper_exclusive.micro - 1}" # noqa: E501
|
|
168
|
+
)
|
|
169
|
+
if upper_exclusive.minor != 0:
|
|
170
|
+
return Version(f"{upper_exclusive.major}.{upper_exclusive.minor - 1}")
|
|
171
|
+
return Version(f"{upper_exclusive.major - 1}")
|
|
172
|
+
|
|
173
|
+
def get_version_range(
|
|
174
|
+
self,
|
|
175
|
+
level: Literal["major", "minor", "micro"] = "major",
|
|
176
|
+
lower_default: str | Version | None = None,
|
|
177
|
+
upper_default: str | Version | None = None,
|
|
178
|
+
) -> list[Version]:
|
|
179
|
+
"""Get the version range.
|
|
180
|
+
|
|
181
|
+
returns a range of versions according to the level
|
|
182
|
+
|
|
183
|
+
E.g. >=3.8, <3.12; level=major -> 3
|
|
184
|
+
>=3.8, <4.12; level=major -> 3, 4
|
|
185
|
+
E.g. >=3.8, <=3.12; level=minor -> 3.8, 3.9, 3.10, 3.11, 3.12
|
|
186
|
+
E.g. >=3.8.1, <=4.12.1; level=micro -> 3.8.1, 3.8.2, ... 4.12.1
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
level: The level of the version to return
|
|
190
|
+
lower_default: The default lower bound if none is specified
|
|
191
|
+
upper_default: The default upper bound if none is specified
|
|
16
192
|
|
|
17
|
-
|
|
193
|
+
Returns:
|
|
194
|
+
A list of versions
|
|
195
|
+
"""
|
|
196
|
+
lower = self.get_lower_inclusive(lower_default)
|
|
197
|
+
upper = self.get_upper_inclusive(upper_default)
|
|
18
198
|
|
|
19
|
-
|
|
199
|
+
if lower is None or upper is None:
|
|
200
|
+
msg = "No lower or upper bound. Please specify default values."
|
|
201
|
+
raise ValueError(msg)
|
|
20
202
|
|
|
21
|
-
|
|
203
|
+
major_level, minor_level, micro_level = range(3)
|
|
204
|
+
level_int = {"major": major_level, "minor": minor_level, "micro": micro_level}[
|
|
205
|
+
level
|
|
206
|
+
]
|
|
207
|
+
lower_as_list = [lower.major, lower.minor, lower.micro]
|
|
208
|
+
upper_as_list = [upper.major, upper.minor, upper.micro]
|
|
22
209
|
|
|
23
|
-
|
|
210
|
+
versions: list[list[int]] = []
|
|
211
|
+
for major in range(lower_as_list[major_level], upper_as_list[major_level] + 1):
|
|
212
|
+
version = [major]
|
|
24
213
|
|
|
25
|
-
|
|
214
|
+
minor_lower_og, minor_upper_og = (
|
|
215
|
+
lower_as_list[minor_level],
|
|
216
|
+
upper_as_list[minor_level],
|
|
217
|
+
)
|
|
218
|
+
diff = minor_upper_og - minor_lower_og
|
|
219
|
+
minor_lower = minor_lower_og if diff >= 0 else 0
|
|
220
|
+
minor_upper = minor_upper_og if diff >= 0 else minor_lower_og + abs(diff)
|
|
221
|
+
for minor in range(
|
|
222
|
+
minor_lower,
|
|
223
|
+
minor_upper + 1,
|
|
224
|
+
):
|
|
225
|
+
# pop the minor if one already exists
|
|
226
|
+
if len(version) > minor_level:
|
|
227
|
+
version.pop()
|
|
26
228
|
|
|
229
|
+
version.append(minor)
|
|
27
230
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
231
|
+
micro_lower_og, micro_upper_og = (
|
|
232
|
+
lower_as_list[micro_level],
|
|
233
|
+
upper_as_list[micro_level],
|
|
234
|
+
)
|
|
235
|
+
diff = micro_upper_og - micro_lower_og
|
|
236
|
+
micro_lower = micro_lower_og if diff >= 0 else 0
|
|
237
|
+
micro_upper = (
|
|
238
|
+
micro_upper_og if diff >= 0 else micro_lower_og + abs(diff)
|
|
239
|
+
)
|
|
240
|
+
for micro in range(
|
|
241
|
+
micro_lower,
|
|
242
|
+
micro_upper + 1,
|
|
243
|
+
):
|
|
244
|
+
version.append(micro)
|
|
245
|
+
versions.append(version[: level_int + 1])
|
|
246
|
+
version.pop()
|
|
247
|
+
version_versions = sorted({Version(".".join(map(str, v))) for v in versions})
|
|
248
|
+
return [v for v in version_versions if self.sset.contains(v)]
|