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.

Files changed (51) hide show
  1. winipedia_utils/artifacts/build.py +78 -0
  2. winipedia_utils/concurrent/concurrent.py +7 -2
  3. winipedia_utils/concurrent/multiprocessing.py +1 -2
  4. winipedia_utils/concurrent/multithreading.py +2 -2
  5. winipedia_utils/data/dataframe/cleaning.py +337 -100
  6. winipedia_utils/git/github/__init__.py +1 -0
  7. winipedia_utils/git/github/github.py +31 -0
  8. winipedia_utils/git/github/repo/__init__.py +1 -0
  9. winipedia_utils/git/github/repo/protect.py +103 -0
  10. winipedia_utils/git/github/repo/repo.py +205 -0
  11. winipedia_utils/git/github/workflows/base/__init__.py +1 -0
  12. winipedia_utils/git/github/workflows/base/base.py +889 -0
  13. winipedia_utils/git/github/workflows/health_check.py +69 -0
  14. winipedia_utils/git/github/workflows/publish.py +51 -0
  15. winipedia_utils/git/github/workflows/release.py +90 -0
  16. winipedia_utils/git/gitignore/config.py +77 -0
  17. winipedia_utils/git/gitignore/gitignore.py +5 -63
  18. winipedia_utils/git/pre_commit/config.py +49 -59
  19. winipedia_utils/git/pre_commit/hooks.py +46 -46
  20. winipedia_utils/git/pre_commit/run_hooks.py +19 -12
  21. winipedia_utils/iterating/iterate.py +63 -1
  22. winipedia_utils/modules/class_.py +69 -12
  23. winipedia_utils/modules/function.py +26 -3
  24. winipedia_utils/modules/inspection.py +56 -0
  25. winipedia_utils/modules/module.py +22 -28
  26. winipedia_utils/modules/package.py +116 -10
  27. winipedia_utils/projects/poetry/config.py +255 -112
  28. winipedia_utils/projects/poetry/poetry.py +230 -13
  29. winipedia_utils/projects/project.py +11 -42
  30. winipedia_utils/setup.py +11 -29
  31. winipedia_utils/testing/config.py +127 -0
  32. winipedia_utils/testing/create_tests.py +5 -19
  33. winipedia_utils/testing/skip.py +19 -0
  34. winipedia_utils/testing/tests/base/fixtures/fixture.py +36 -0
  35. winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +3 -3
  36. winipedia_utils/testing/tests/base/fixtures/scopes/module.py +9 -6
  37. winipedia_utils/testing/tests/base/fixtures/scopes/session.py +27 -176
  38. winipedia_utils/testing/tests/base/utils/utils.py +27 -57
  39. winipedia_utils/text/config.py +250 -0
  40. winipedia_utils/text/string.py +30 -0
  41. winipedia_utils-0.6.6.dist-info/METADATA +390 -0
  42. {winipedia_utils-0.2.63.dist-info → winipedia_utils-0.6.6.dist-info}/RECORD +46 -34
  43. winipedia_utils/consts.py +0 -21
  44. winipedia_utils/git/workflows/base/base.py +0 -77
  45. winipedia_utils/git/workflows/publish.py +0 -79
  46. winipedia_utils/git/workflows/release.py +0 -91
  47. winipedia_utils-0.2.63.dist-info/METADATA +0 -738
  48. /winipedia_utils/{git/workflows/base → artifacts}/__init__.py +0 -0
  49. /winipedia_utils/git/{workflows → github/workflows}/__init__.py +0 -0
  50. {winipedia_utils-0.2.63.dist-info → winipedia_utils-0.6.6.dist-info}/WHEEL +0 -0
  51. {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 tomlkit
7
- from tomlkit.toml_document import TOMLDocument
8
-
9
- from winipedia_utils.projects.poetry.poetry import logger
10
-
11
-
12
- def laod_pyproject_toml() -> TOMLDocument:
13
- """Load the pyproject.toml file."""
14
- return tomlkit.parse(Path("pyproject.toml").read_text())
15
-
16
-
17
- def get_dev_dependencies_from_pyproject_toml() -> set[str]:
18
- """Get the dev dependencies from pyproject.toml."""
19
- toml = laod_pyproject_toml()
20
- dev_dependencies = (
21
- toml.get("tool", {})
22
- .get("poetry", {})
23
- .get("group", {})
24
- .get("dev", {})
25
- .get("dependencies", {})
26
- .keys()
27
- )
28
- if not dev_dependencies:
29
- dev_dependencies = toml.get("dependency-groups", {}).get("dev", [])
30
- dev_dependencies = [d.split("(")[0].strip() for d in dev_dependencies]
31
- return set(dev_dependencies)
32
-
33
-
34
- def dump_pyproject_toml(toml: TOMLDocument) -> None:
35
- """Dump the pyproject.toml file."""
36
- with Path("pyproject.toml").open("w") as f:
37
- tomlkit.dump(toml, f)
38
-
39
-
40
- def get_poetry_package_name() -> str:
41
- """Get the name of the project from pyproject.toml."""
42
- toml = laod_pyproject_toml()
43
- project_dict = toml.get("project", {})
44
- project_name = str(project_dict.get("name", ""))
45
- return project_name.replace("-", "_")
46
-
47
-
48
- def _get_pyproject_toml_tool_configs() -> dict[str, Any]:
49
- """Get the tool configurations for pyproject.toml."""
50
- return {
51
- "ruff": {
52
- "exclude": [".*", "**/migrations/*.py"],
53
- "lint": {
54
- "select": ["ALL"],
55
- "ignore": ["D203", "D213", "COM812", "ANN401"],
56
- "fixable": ["ALL"],
57
- "pydocstyle": {
58
- "convention": "google",
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
- "mypy": {
63
- "strict": True,
64
- "warn_unreachable": True,
65
- "show_error_codes": True,
66
- "files": ".",
67
- },
68
- "pytest": {
69
- "ini_options": {
70
- "testpaths": ["tests"],
71
- }
72
- },
73
- "bandit": {},
74
- }
75
-
76
-
77
- def _tool_config_is_correct(tool: str, config: dict[str, Any]) -> bool:
78
- """Check if the tool configuration in pyproject.toml is correct."""
79
- toml = laod_pyproject_toml()
80
- actual_tools = toml.get("tool", {})
81
-
82
- return bool(actual_tools.get(tool) == config)
83
-
84
-
85
- def _pyproject_tool_configs_are_correct() -> bool:
86
- """Check if the tool configurations in pyproject.toml are correct."""
87
- expected_tool_dict = _get_pyproject_toml_tool_configs()
88
- for tool, config in expected_tool_dict.items():
89
- if not _tool_config_is_correct(tool, config):
90
- return False
91
-
92
- return True
93
-
94
-
95
- def _add_configurations_to_pyproject_toml() -> None:
96
- """Add tool.* configurations to pyproject.toml."""
97
- expected_tool_dict = _get_pyproject_toml_tool_configs()
98
- toml = laod_pyproject_toml()
99
- actual_tool_dict = toml.get("tool", None)
100
- if actual_tool_dict is None:
101
- # add tool section
102
- toml.add("tool", tomlkit.table())
103
-
104
- actual_tool_dict = toml.get("tool", None)
105
- if actual_tool_dict is None:
106
- msg = "tool section is None after adding it"
107
- raise ValueError(msg)
108
-
109
- # update the toml dct and dump it but only update the tools specified not all tools
110
- for tool, config in expected_tool_dict.items():
111
- # if tool section already exists skip it
112
- if not _tool_config_is_correct(tool, config):
113
- logger.info("Adding tool.%s configuration to pyproject.toml", tool)
114
- # updates inplace of toml_dict["tool"][tool]
115
- actual_tool_dict[tool] = config
116
-
117
- dump_pyproject_toml(toml)
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 sys
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
- POETRY_PATH = which_with_raise("poetry", raise_error=False) or "poetry"
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
- POETRY_RUN_ARGS = [POETRY_PATH, "run"]
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
- POETRY_ADD_ARGS = [POETRY_PATH, "add"]
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
- POETRY_ADD_DEV_ARGS = [*POETRY_ADD_ARGS, "--group", "dev"]
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
- POETRY_RUN_PYTHON_ARGS = [*POETRY_RUN_ARGS, sys.executable]
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
- POETRY_RUN_RUFF_ARGS = [*POETRY_RUN_ARGS, "ruff"]
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
- def _install_dev_dependencies() -> None:
29
- """Install winipedia_utils dev dependencies as dev dependencies."""
30
- logger.info("Adding dev dependencies: %s", _DEV_DEPENDENCIES)
31
- run_subprocess([*POETRY_ADD_DEV_ARGS, *_DEV_DEPENDENCIES], check=True)
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)]