winipedia-utils 0.4.48__py3-none-any.whl → 0.6.12__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.
- winipedia_utils/artifacts/__init__.py +1 -0
- winipedia_utils/artifacts/build.py +78 -0
- winipedia_utils/git/github/workflows/base/base.py +794 -199
- winipedia_utils/git/github/workflows/health_check.py +44 -32
- winipedia_utils/git/github/workflows/publish.py +25 -23
- winipedia_utils/git/github/workflows/release.py +58 -13
- winipedia_utils/git/pre_commit/config.py +3 -3
- winipedia_utils/git/pre_commit/hooks.py +11 -73
- winipedia_utils/git/pre_commit/run_hooks.py +18 -8
- winipedia_utils/modules/class_.py +11 -4
- winipedia_utils/modules/function.py +9 -3
- winipedia_utils/modules/inspection.py +56 -0
- winipedia_utils/modules/module.py +2 -32
- winipedia_utils/modules/package.py +3 -29
- winipedia_utils/os/os.py +2 -2
- winipedia_utils/projects/poetry/config.py +114 -29
- winipedia_utils/projects/poetry/dev_deps.py +21 -0
- winipedia_utils/projects/poetry/poetry.py +122 -3
- winipedia_utils/projects/project.py +1 -2
- winipedia_utils/setup.py +5 -1
- winipedia_utils/testing/create_tests.py +1 -1
- winipedia_utils/text/config.py +3 -3
- winipedia_utils/text/string.py +33 -0
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/METADATA +52 -19
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/RECORD +27 -23
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/WHEEL +0 -0
- {winipedia_utils-0.4.48.dist-info → winipedia_utils-0.6.12.dist-info}/licenses/LICENSE +0 -0
|
@@ -50,7 +50,7 @@ def get_src_package() -> ModuleType:
|
|
|
50
50
|
TESTS_PACKAGE_NAME,
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
-
packages = find_packages_as_modules(depth=0)
|
|
53
|
+
packages = find_packages_as_modules(depth=0, include_namespace_packages=True)
|
|
54
54
|
return next(p for p in packages if p.__name__ != TESTS_PACKAGE_NAME)
|
|
55
55
|
|
|
56
56
|
|
|
@@ -409,33 +409,6 @@ def get_main_package() -> ModuleType:
|
|
|
409
409
|
raise ValueError(msg)
|
|
410
410
|
|
|
411
411
|
|
|
412
|
-
def make_name_from_package(
|
|
413
|
-
package: ModuleType,
|
|
414
|
-
split_on: str = "_",
|
|
415
|
-
join_on: str = "-",
|
|
416
|
-
*,
|
|
417
|
-
capitalize: bool = True,
|
|
418
|
-
) -> str:
|
|
419
|
-
"""Make a name from a package.
|
|
420
|
-
|
|
421
|
-
takes a package and makes a name from it that is readable by humans.
|
|
422
|
-
|
|
423
|
-
Args:
|
|
424
|
-
package (ModuleType): The package to make a name from
|
|
425
|
-
split_on (str, optional): what to split the package name on. Defaults to "_".
|
|
426
|
-
join_on (str, optional): what to join the package name with. Defaults to "-".
|
|
427
|
-
capitalize (bool, optional): Whether to capitalize each part. Defaults to True.
|
|
428
|
-
|
|
429
|
-
Returns:
|
|
430
|
-
str: _description_
|
|
431
|
-
"""
|
|
432
|
-
package_name = package.__name__.split(".")[-1]
|
|
433
|
-
parts = package_name.split(split_on)
|
|
434
|
-
if capitalize:
|
|
435
|
-
parts = [part.capitalize() for part in parts]
|
|
436
|
-
return join_on.join(parts)
|
|
437
|
-
|
|
438
|
-
|
|
439
412
|
class DependencyGraph(nx.DiGraph): # type: ignore [type-arg]
|
|
440
413
|
"""A directed graph representing Python package dependencies."""
|
|
441
414
|
|
|
@@ -472,7 +445,8 @@ class DependencyGraph(nx.DiGraph): # type: ignore [type-arg]
|
|
|
472
445
|
def parse_pkg_name_from_req(req: str) -> str | None:
|
|
473
446
|
"""Extract the bare dependency name from a requirement string."""
|
|
474
447
|
# split on the first non alphanumeric character like >, <, =, etc.
|
|
475
|
-
|
|
448
|
+
# keep - and _ for names like winipedia-utils or winipedia_utils
|
|
449
|
+
dep = re.split(r"[^a-zA-Z0-9_-]", req.strip())[0].strip()
|
|
476
450
|
return DependencyGraph.normalize_package_name(dep) if dep else None
|
|
477
451
|
|
|
478
452
|
def get_all_depending_on(
|
winipedia_utils/os/os.py
CHANGED
|
@@ -7,7 +7,7 @@ These utilities help with system-level operations and configuration.
|
|
|
7
7
|
|
|
8
8
|
import shutil
|
|
9
9
|
import subprocess # nosec: B404
|
|
10
|
-
from
|
|
10
|
+
from collections.abc import Sequence
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
|
|
@@ -34,7 +34,7 @@ def which_with_raise(cmd: str, *, raise_error: bool = True) -> str | None:
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def run_subprocess(
|
|
37
|
-
args:
|
|
37
|
+
args: Sequence[str],
|
|
38
38
|
*,
|
|
39
39
|
input_: str | bytes | None = None,
|
|
40
40
|
capture_output: bool = True,
|
|
@@ -1,31 +1,62 @@
|
|
|
1
1
|
"""Config utilities for poetry and pyproject.toml."""
|
|
2
2
|
|
|
3
|
+
from functools import cache
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
from typing import Any, cast
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
from
|
|
7
|
+
import requests
|
|
8
|
+
from packaging.version import Version
|
|
9
|
+
|
|
10
|
+
from winipedia_utils.os.os import run_subprocess
|
|
11
|
+
from winipedia_utils.projects.poetry.dev_deps import DEV_DEPENDENCIES
|
|
12
|
+
from winipedia_utils.projects.poetry.poetry import POETRY_ARG, VersionConstraint
|
|
8
13
|
from winipedia_utils.testing.config import ExperimentConfigFile
|
|
9
14
|
from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
|
|
10
15
|
from winipedia_utils.text.config import ConfigFile, TomlConfigFile
|
|
16
|
+
from winipedia_utils.text.string import make_name_from_obj
|
|
11
17
|
|
|
12
18
|
|
|
13
19
|
class PyprojectConfigFile(TomlConfigFile):
|
|
14
20
|
"""Config file for pyproject.toml."""
|
|
15
21
|
|
|
22
|
+
@classmethod
|
|
23
|
+
def dump(cls, config: dict[str, Any] | list[Any]) -> None:
|
|
24
|
+
"""Dump the config file.
|
|
25
|
+
|
|
26
|
+
We remove the wrong dependencies from the config before dumping.
|
|
27
|
+
So we do not want dependencies under tool.poetry.dependencies but
|
|
28
|
+
under project.dependencies. And we do not want dev dependencies under
|
|
29
|
+
tool.poetry.dev-dependencies but under tool.poetry.group.dev.dependencies.
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(config, dict):
|
|
32
|
+
msg = f"Cannot dump {config} to pyproject.toml file."
|
|
33
|
+
raise TypeError(msg)
|
|
34
|
+
config = cls.remove_wrong_dependencies(config)
|
|
35
|
+
super().dump(config)
|
|
36
|
+
|
|
16
37
|
@classmethod
|
|
17
38
|
def get_parent_path(cls) -> Path:
|
|
18
39
|
"""Get the path to the config file."""
|
|
19
40
|
return Path()
|
|
20
41
|
|
|
42
|
+
@classmethod
|
|
43
|
+
def get_repository_name(cls) -> str:
|
|
44
|
+
"""Get the repository name.
|
|
45
|
+
|
|
46
|
+
Is the parent folder the project ives in and should be the same as the
|
|
47
|
+
project name.
|
|
48
|
+
"""
|
|
49
|
+
cwd = Path.cwd()
|
|
50
|
+
return cwd.name
|
|
51
|
+
|
|
21
52
|
@classmethod
|
|
22
53
|
def get_configs(cls) -> dict[str, Any]:
|
|
23
54
|
"""Get the config."""
|
|
24
55
|
return {
|
|
25
56
|
"project": {
|
|
26
|
-
"name":
|
|
57
|
+
"name": make_name_from_obj(cls.get_repository_name(), capitalize=False),
|
|
27
58
|
"readme": "README.md",
|
|
28
|
-
"
|
|
59
|
+
"dependencies": list(cls.get_dependencies()),
|
|
29
60
|
},
|
|
30
61
|
"build-system": {
|
|
31
62
|
"requires": ["poetry-core>=2.0.0,<3.0.0"],
|
|
@@ -33,15 +64,11 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
33
64
|
},
|
|
34
65
|
"tool": {
|
|
35
66
|
"poetry": {
|
|
36
|
-
"packages": [{"include":
|
|
37
|
-
"dependencies": dict.fromkeys(
|
|
38
|
-
cls.get_dependencies(),
|
|
39
|
-
"*",
|
|
40
|
-
),
|
|
67
|
+
"packages": [{"include": cls.get_repository_name()}],
|
|
41
68
|
"group": {
|
|
42
69
|
"dev": {
|
|
43
70
|
"dependencies": dict.fromkeys(
|
|
44
|
-
cls.get_dev_dependencies(),
|
|
71
|
+
cls.get_dev_dependencies() | DEV_DEPENDENCIES,
|
|
45
72
|
"*",
|
|
46
73
|
)
|
|
47
74
|
}
|
|
@@ -76,6 +103,29 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
76
103
|
package_name = str(project_dict.get("name", ""))
|
|
77
104
|
return package_name.replace("-", "_")
|
|
78
105
|
|
|
106
|
+
@classmethod
|
|
107
|
+
def remove_wrong_dependencies(cls, config: dict[str, Any]) -> dict[str, Any]:
|
|
108
|
+
"""Remove the wrong dependencies from the config."""
|
|
109
|
+
# raise if the right sections do not exist
|
|
110
|
+
if config.get("project", {}).get("dependencies") is None:
|
|
111
|
+
msg = "No dependencies section in config"
|
|
112
|
+
raise ValueError(msg)
|
|
113
|
+
|
|
114
|
+
if (
|
|
115
|
+
config.get("tool", {}).get("poetry", {}).get("group", {}).get("dev", {})
|
|
116
|
+
is None
|
|
117
|
+
):
|
|
118
|
+
msg = "No dev dependencies section in config"
|
|
119
|
+
raise ValueError(msg)
|
|
120
|
+
|
|
121
|
+
# remove the wrong dependencies sections if they exist
|
|
122
|
+
if config.get("tool", {}).get("poetry", {}).get("dependencies") is not None:
|
|
123
|
+
del config["tool"]["poetry"]["dependencies"]
|
|
124
|
+
if config.get("tool", {}).get("poetry", {}).get("dev-dependencies") is not None:
|
|
125
|
+
del config["tool"]["poetry"]["dev-dependencies"]
|
|
126
|
+
|
|
127
|
+
return config
|
|
128
|
+
|
|
79
129
|
@classmethod
|
|
80
130
|
def get_all_dependencies(cls) -> set[str]:
|
|
81
131
|
"""Get all dependencies."""
|
|
@@ -103,9 +153,17 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
103
153
|
@classmethod
|
|
104
154
|
def get_dependencies(cls) -> set[str]:
|
|
105
155
|
"""Get the dependencies."""
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
156
|
+
deps = set(cls.load().get("project", {}).get("dependencies", {}))
|
|
157
|
+
deps = {d.split("(")[0].strip() for d in deps}
|
|
158
|
+
if not deps:
|
|
159
|
+
deps = set(
|
|
160
|
+
cls.load()
|
|
161
|
+
.get("tool", {})
|
|
162
|
+
.get("poetry", {})
|
|
163
|
+
.get("dependencies", {})
|
|
164
|
+
.keys()
|
|
165
|
+
)
|
|
166
|
+
return deps
|
|
109
167
|
|
|
110
168
|
@classmethod
|
|
111
169
|
def get_expected_dev_dependencies(cls) -> set[str]:
|
|
@@ -135,25 +193,29 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
135
193
|
return cls.get_main_author()["name"]
|
|
136
194
|
|
|
137
195
|
@classmethod
|
|
138
|
-
|
|
196
|
+
@cache
|
|
197
|
+
def fetch_latest_python_version(cls) -> Version:
|
|
198
|
+
"""Fetch the latest python version from python.org."""
|
|
199
|
+
url = "https://endoflife.date/api/python.json"
|
|
200
|
+
resp = requests.get(url, timeout=10)
|
|
201
|
+
resp.raise_for_status()
|
|
202
|
+
data = resp.json()
|
|
203
|
+
# first element has metadata for latest stable
|
|
204
|
+
latest_version = data[0]["latest"]
|
|
205
|
+
return Version(latest_version)
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def get_latest_possible_python_version(cls) -> Version:
|
|
139
209
|
"""Get the latest possible python version."""
|
|
140
210
|
constraint = cls.load()["project"]["requires-python"]
|
|
141
211
|
version_constraint = VersionConstraint(constraint)
|
|
142
|
-
|
|
143
|
-
if
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# convert to inclusive
|
|
147
|
-
if upper.micro != 0:
|
|
148
|
-
micro = upper.micro - 1
|
|
149
|
-
return f"{upper.major}.{upper.minor}" + (f".{micro}" if micro != 0 else "")
|
|
150
|
-
if upper.minor != 0:
|
|
151
|
-
minor = upper.minor - 1
|
|
152
|
-
return f"{upper.major}" + (f".{minor}" if minor != 0 else "")
|
|
153
|
-
return f"{upper.major - 1}.x"
|
|
212
|
+
version = version_constraint.get_upper_inclusive()
|
|
213
|
+
if version is None:
|
|
214
|
+
version = cls.fetch_latest_python_version()
|
|
215
|
+
return version
|
|
154
216
|
|
|
155
217
|
@classmethod
|
|
156
|
-
def get_first_supported_python_version(cls) ->
|
|
218
|
+
def get_first_supported_python_version(cls) -> Version:
|
|
157
219
|
"""Get the first supported python version."""
|
|
158
220
|
constraint = cls.load()["project"]["requires-python"]
|
|
159
221
|
version_constraint = VersionConstraint(constraint)
|
|
@@ -161,7 +223,28 @@ class PyprojectConfigFile(TomlConfigFile):
|
|
|
161
223
|
if lower is None:
|
|
162
224
|
msg = "Need a lower bound for python version"
|
|
163
225
|
raise ValueError(msg)
|
|
164
|
-
return
|
|
226
|
+
return lower
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def get_supported_python_versions(cls) -> list[Version]:
|
|
230
|
+
"""Get all supported python versions."""
|
|
231
|
+
constraint = cls.load()["project"]["requires-python"]
|
|
232
|
+
version_constraint = VersionConstraint(constraint)
|
|
233
|
+
return version_constraint.get_version_range(
|
|
234
|
+
level="minor", upper_default=cls.fetch_latest_python_version()
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
def update_poetry(cls) -> None:
|
|
239
|
+
"""Update poetry."""
|
|
240
|
+
args = [POETRY_ARG, "self", "update"]
|
|
241
|
+
run_subprocess(args)
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
def update_with_dev(cls) -> None:
|
|
245
|
+
"""Install all dependencies with dev."""
|
|
246
|
+
args = [POETRY_ARG, "update", "--with", "dev"]
|
|
247
|
+
run_subprocess(args)
|
|
165
248
|
|
|
166
249
|
|
|
167
250
|
class TypedConfigFile(ConfigFile):
|
|
@@ -223,7 +306,9 @@ class DotPythonVersionConfigFile(ConfigFile):
|
|
|
223
306
|
def get_configs(cls) -> dict[str, Any]:
|
|
224
307
|
"""Get the config."""
|
|
225
308
|
return {
|
|
226
|
-
cls.VERSION_KEY:
|
|
309
|
+
cls.VERSION_KEY: str(
|
|
310
|
+
PyprojectConfigFile.get_first_supported_python_version()
|
|
311
|
+
)
|
|
227
312
|
}
|
|
228
313
|
|
|
229
314
|
@classmethod
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Contains a dict with the dev dependencies.
|
|
2
|
+
|
|
3
|
+
For poetry when winipedia_utils is a dependency.
|
|
4
|
+
winipedia_utils will add these automatically to the pyproject.toml file.
|
|
5
|
+
winipedia utils PyprojectConfigFile will auto dump the config here so it can access it
|
|
6
|
+
when being a dependency in another project.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
DEV_DEPENDENCIES: set[str] = {
|
|
10
|
+
"ruff",
|
|
11
|
+
"types-networkx",
|
|
12
|
+
"types-defusedxml",
|
|
13
|
+
"types-pyyaml",
|
|
14
|
+
"pytest",
|
|
15
|
+
"types-setuptools",
|
|
16
|
+
"pytest-mock",
|
|
17
|
+
"bandit",
|
|
18
|
+
"pre-commit",
|
|
19
|
+
"mypy",
|
|
20
|
+
"types-tqdm",
|
|
21
|
+
}
|
|
@@ -5,6 +5,7 @@ This module provides utility functions for working with Python projects
|
|
|
5
5
|
|
|
6
6
|
from collections.abc import Iterable
|
|
7
7
|
from types import ModuleType
|
|
8
|
+
from typing import Literal
|
|
8
9
|
|
|
9
10
|
from packaging.specifiers import SpecifierSet
|
|
10
11
|
from packaging.version import Version
|
|
@@ -35,6 +36,11 @@ def get_run_python_module_args(module: ModuleType) -> list[str]:
|
|
|
35
36
|
return [*RUN_PYTHON_MODULE_ARGS, make_obj_importpath(module)]
|
|
36
37
|
|
|
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
|
+
|
|
38
44
|
def get_python_module_script(module: ModuleType) -> str:
|
|
39
45
|
"""Get the script to run a module."""
|
|
40
46
|
return get_script_from_args(get_run_python_module_args(module))
|
|
@@ -42,7 +48,7 @@ def get_python_module_script(module: ModuleType) -> str:
|
|
|
42
48
|
|
|
43
49
|
def get_poetry_run_module_script(module: ModuleType) -> str:
|
|
44
50
|
"""Get the script to run a module."""
|
|
45
|
-
return get_script_from_args(
|
|
51
|
+
return get_script_from_args(get_poetry_run_module_args(module))
|
|
46
52
|
|
|
47
53
|
|
|
48
54
|
class VersionConstraint:
|
|
@@ -92,7 +98,9 @@ class VersionConstraint:
|
|
|
92
98
|
max(self.lowers_inclusive) if self.lowers_inclusive else None
|
|
93
99
|
)
|
|
94
100
|
|
|
95
|
-
def get_lower_inclusive(
|
|
101
|
+
def get_lower_inclusive(
|
|
102
|
+
self, default: str | Version | None = None
|
|
103
|
+
) -> Version | None:
|
|
96
104
|
"""Get the minimum version.
|
|
97
105
|
|
|
98
106
|
Is given inclusive. E.g. >=3.8, <3.12 -> 3.8
|
|
@@ -106,12 +114,15 @@ class VersionConstraint:
|
|
|
106
114
|
Returns:
|
|
107
115
|
The minimum version
|
|
108
116
|
"""
|
|
117
|
+
default = str(default) if default else None
|
|
109
118
|
if self.lower_inclusive is None:
|
|
110
119
|
return Version(default) if default else None
|
|
111
120
|
|
|
112
121
|
return self.lower_inclusive
|
|
113
122
|
|
|
114
|
-
def get_upper_exclusive(
|
|
123
|
+
def get_upper_exclusive(
|
|
124
|
+
self, default: str | Version | None = None
|
|
125
|
+
) -> Version | None:
|
|
115
126
|
"""Get the maximum version.
|
|
116
127
|
|
|
117
128
|
Is given exclusive. E.g. >=3.8, <3.12 -> 3.12
|
|
@@ -123,7 +134,115 @@ class VersionConstraint:
|
|
|
123
134
|
Returns:
|
|
124
135
|
The maximum version
|
|
125
136
|
"""
|
|
137
|
+
default = str(default) if default else None
|
|
126
138
|
if self.upper_exclusive is None:
|
|
127
139
|
return Version(default) if default else None
|
|
128
140
|
|
|
129
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
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
A list of versions
|
|
195
|
+
"""
|
|
196
|
+
lower = self.get_lower_inclusive(lower_default)
|
|
197
|
+
upper = self.get_upper_inclusive(upper_default)
|
|
198
|
+
|
|
199
|
+
if lower is None or upper is None:
|
|
200
|
+
msg = "No lower or upper bound. Please specify default values."
|
|
201
|
+
raise ValueError(msg)
|
|
202
|
+
|
|
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]
|
|
209
|
+
|
|
210
|
+
versions: list[list[int]] = []
|
|
211
|
+
for major in range(lower_as_list[major_level], upper_as_list[major_level] + 1):
|
|
212
|
+
version = [major]
|
|
213
|
+
|
|
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()
|
|
228
|
+
|
|
229
|
+
version.append(minor)
|
|
230
|
+
|
|
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)]
|
|
@@ -11,7 +11,6 @@ from winipedia_utils.text.config import (
|
|
|
11
11
|
|
|
12
12
|
def create_project_root() -> None:
|
|
13
13
|
"""Create the project root."""
|
|
14
|
-
ConfigFile.init_config_files()
|
|
15
|
-
|
|
16
14
|
src_package_name = PyprojectConfigFile.get_package_name()
|
|
17
15
|
create_module(src_package_name, is_package=True)
|
|
16
|
+
ConfigFile.init_config_files()
|
winipedia_utils/setup.py
CHANGED
|
@@ -12,13 +12,17 @@ from typing import Any
|
|
|
12
12
|
from winipedia_utils.git.gitignore.config import GitIgnoreConfigFile
|
|
13
13
|
from winipedia_utils.git.pre_commit.run_hooks import run_hooks
|
|
14
14
|
from winipedia_utils.logging.logger import get_logger
|
|
15
|
+
from winipedia_utils.projects.poetry.config import PyprojectConfigFile
|
|
15
16
|
from winipedia_utils.projects.project import create_project_root
|
|
16
17
|
|
|
17
18
|
logger = get_logger(__name__)
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
SETUP_STEPS: list[Callable[..., Any]] = [
|
|
21
|
-
GitIgnoreConfigFile, # must be
|
|
22
|
+
GitIgnoreConfigFile, # must be before create_project_root
|
|
23
|
+
PyprojectConfigFile, # must be before create_project_root
|
|
24
|
+
PyprojectConfigFile.update_poetry, # must be before create_project_root
|
|
25
|
+
PyprojectConfigFile.update_with_dev, # must be before create_project_root
|
|
22
26
|
create_project_root,
|
|
23
27
|
run_hooks,
|
|
24
28
|
]
|
|
@@ -14,11 +14,11 @@ from winipedia_utils.modules.class_ import (
|
|
|
14
14
|
get_all_methods_from_cls,
|
|
15
15
|
)
|
|
16
16
|
from winipedia_utils.modules.function import get_all_functions_from_module
|
|
17
|
+
from winipedia_utils.modules.inspection import get_qualname_of_obj
|
|
17
18
|
from winipedia_utils.modules.module import (
|
|
18
19
|
create_module,
|
|
19
20
|
get_isolated_obj_name,
|
|
20
21
|
get_module_content_as_str,
|
|
21
|
-
get_qualname_of_obj,
|
|
22
22
|
to_path,
|
|
23
23
|
)
|
|
24
24
|
from winipedia_utils.modules.package import (
|
winipedia_utils/text/config.py
CHANGED
|
@@ -13,7 +13,7 @@ from winipedia_utils.iterating.iterate import nested_structure_is_subset
|
|
|
13
13
|
from winipedia_utils.modules.class_ import init_all_nonabstract_subclasses
|
|
14
14
|
from winipedia_utils.modules.package import DependencyGraph, get_src_package
|
|
15
15
|
from winipedia_utils.projects.poetry.poetry import (
|
|
16
|
-
|
|
16
|
+
get_poetry_run_module_script,
|
|
17
17
|
)
|
|
18
18
|
from winipedia_utils.text.string import split_on_uppercase
|
|
19
19
|
|
|
@@ -158,11 +158,11 @@ class ConfigFile(ABC):
|
|
|
158
158
|
init_all_nonabstract_subclasses(cls, load_package_before=pkg)
|
|
159
159
|
|
|
160
160
|
@staticmethod
|
|
161
|
-
def
|
|
161
|
+
def get_poetry_run_setup_script() -> str:
|
|
162
162
|
"""Get the poetry run setup script."""
|
|
163
163
|
from winipedia_utils import setup # noqa: PLC0415 # avoid circular import
|
|
164
164
|
|
|
165
|
-
return
|
|
165
|
+
return get_poetry_run_module_script(setup)
|
|
166
166
|
|
|
167
167
|
|
|
168
168
|
class YamlConfigFile(ConfigFile):
|
winipedia_utils/text/string.py
CHANGED
|
@@ -7,7 +7,10 @@ These utilities simplify common string manipulation tasks throughout the applica
|
|
|
7
7
|
|
|
8
8
|
import hashlib
|
|
9
9
|
import textwrap
|
|
10
|
+
from collections.abc import Callable
|
|
10
11
|
from io import StringIO
|
|
12
|
+
from types import ModuleType
|
|
13
|
+
from typing import Any
|
|
11
14
|
|
|
12
15
|
from defusedxml import ElementTree as DefusedElementTree
|
|
13
16
|
|
|
@@ -124,3 +127,33 @@ def split_on_uppercase(string: str) -> list[str]:
|
|
|
124
127
|
current_part += letter
|
|
125
128
|
parts.append(current_part)
|
|
126
129
|
return parts
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def make_name_from_obj(
|
|
133
|
+
package: ModuleType | Callable[..., Any] | type | str,
|
|
134
|
+
split_on: str = "_",
|
|
135
|
+
join_on: str = "-",
|
|
136
|
+
*,
|
|
137
|
+
capitalize: bool = True,
|
|
138
|
+
) -> str:
|
|
139
|
+
"""Make a name from a package.
|
|
140
|
+
|
|
141
|
+
takes a package and makes a name from it that is readable by humans.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
package (ModuleType): The package to make a name from
|
|
145
|
+
split_on (str, optional): what to split the package name on. Defaults to "_".
|
|
146
|
+
join_on (str, optional): what to join the package name with. Defaults to "-".
|
|
147
|
+
capitalize (bool, optional): Whether to capitalize each part. Defaults to True.
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
str: _description_
|
|
151
|
+
"""
|
|
152
|
+
if not isinstance(package, str):
|
|
153
|
+
package_name = package.__name__.split(".")[-1]
|
|
154
|
+
else:
|
|
155
|
+
package_name = package
|
|
156
|
+
parts = package_name.split(split_on)
|
|
157
|
+
if capitalize:
|
|
158
|
+
parts = [part.capitalize() for part in parts]
|
|
159
|
+
return join_on.join(parts)
|