winipedia-utils 0.4.43__py3-none-any.whl → 0.7.1__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 (120) hide show
  1. winipedia_utils/artifacts/build.py +27 -0
  2. winipedia_utils/dev/artifacts/build.py +62 -0
  3. winipedia_utils/{text → dev/configs/base}/config.py +16 -11
  4. winipedia_utils/{git/gitignore/config.py → dev/configs/gitignore.py} +2 -2
  5. winipedia_utils/{git/pre_commit/config.py → dev/configs/pre_commit.py} +6 -6
  6. winipedia_utils/{projects/poetry/config.py → dev/configs/pyproject.py} +120 -32
  7. winipedia_utils/{testing/config.py → dev/configs/testing.py} +7 -4
  8. winipedia_utils/dev/configs/workflows/base/base.py +907 -0
  9. winipedia_utils/dev/configs/workflows/health_check.py +69 -0
  10. winipedia_utils/dev/configs/workflows/publish.py +51 -0
  11. winipedia_utils/dev/configs/workflows/release.py +91 -0
  12. winipedia_utils/dev/git/github/repo/__init__.py +1 -0
  13. winipedia_utils/{git → dev/git}/github/repo/protect.py +5 -5
  14. winipedia_utils/dev/git/pre_commit/hooks.py +85 -0
  15. winipedia_utils/{git → dev/git}/pre_commit/run_hooks.py +23 -13
  16. winipedia_utils/dev/projects/poetry/dev_deps.py +21 -0
  17. winipedia_utils/dev/projects/poetry/poetry.py +248 -0
  18. winipedia_utils/{projects → dev/projects}/project.py +6 -7
  19. winipedia_utils/dev/testing/__init__.py +1 -0
  20. winipedia_utils/{testing → dev/testing}/convention.py +1 -1
  21. winipedia_utils/{testing → dev/testing}/create_tests.py +14 -14
  22. winipedia_utils/dev/testing/tests/__init__.py +1 -0
  23. winipedia_utils/dev/testing/tests/base/__init__.py +1 -0
  24. winipedia_utils/dev/testing/tests/base/fixtures/__init__.py +1 -0
  25. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/fixture.py +1 -1
  26. winipedia_utils/dev/testing/tests/base/fixtures/scopes/__init__.py +1 -0
  27. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/class_.py +2 -2
  28. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/module.py +2 -2
  29. winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/session.py +10 -10
  30. winipedia_utils/dev/testing/tests/base/utils/__init__.py +1 -0
  31. winipedia_utils/dev/testing/tests/base/utils/utils.py +1 -0
  32. winipedia_utils/{testing → dev/testing}/tests/conftest.py +2 -2
  33. winipedia_utils/{testing/tests/base/utils → dev/testing}/utils.py +7 -24
  34. winipedia_utils/setup.py +9 -5
  35. winipedia_utils/utils/__init__.py +1 -0
  36. winipedia_utils/utils/data/dataframe/__init__.py +1 -0
  37. winipedia_utils/{data → utils/data}/dataframe/cleaning.py +1 -1
  38. winipedia_utils/utils/data/structures/__init__.py +1 -0
  39. winipedia_utils/{text → utils/data/structures/text}/string.py +36 -3
  40. winipedia_utils/utils/git/__init__.py +1 -0
  41. winipedia_utils/utils/git/github/__init__.py +1 -0
  42. winipedia_utils/{git → utils/git}/github/github.py +1 -1
  43. winipedia_utils/utils/git/github/repo/__init__.py +1 -0
  44. winipedia_utils/{git → utils/git}/github/repo/repo.py +1 -1
  45. winipedia_utils/{git → utils/git}/gitignore/gitignore.py +2 -2
  46. winipedia_utils/{concurrent → utils/iterating/concurrent}/concurrent.py +4 -4
  47. winipedia_utils/{concurrent → utils/iterating/concurrent}/multiprocessing.py +2 -2
  48. winipedia_utils/{concurrent → utils/iterating/concurrent}/multithreading.py +1 -1
  49. winipedia_utils/{logging → utils/logging}/logger.py +1 -1
  50. winipedia_utils/{modules → utils/modules}/class_.py +15 -8
  51. winipedia_utils/{modules → utils/modules}/function.py +10 -4
  52. winipedia_utils/utils/modules/inspection.py +56 -0
  53. winipedia_utils/{modules → utils/modules}/module.py +27 -32
  54. winipedia_utils/{modules → utils/modules}/package.py +100 -34
  55. winipedia_utils/{oop → utils/oop}/mixins/meta.py +4 -4
  56. winipedia_utils/{oop → utils/oop}/mixins/mixin.py +2 -2
  57. winipedia_utils/{os → utils/os}/os.py +2 -2
  58. winipedia_utils/utils/resources/__init__.py +1 -0
  59. winipedia_utils/utils/resources/svgs/__init__.py +1 -0
  60. winipedia_utils/{resources → utils/resources}/svgs/svg.py +1 -1
  61. winipedia_utils/utils/testing/__init__.py +1 -0
  62. winipedia_utils/{testing → utils/testing}/assertions.py +18 -0
  63. winipedia_utils/{testing → utils/testing}/skip.py +1 -1
  64. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/METADATA +71 -36
  65. winipedia_utils-0.7.1.dist-info/RECORD +109 -0
  66. winipedia_utils/git/github/workflows/base/base.py +0 -294
  67. winipedia_utils/git/github/workflows/health_check.py +0 -57
  68. winipedia_utils/git/github/workflows/publish.py +0 -49
  69. winipedia_utils/git/github/workflows/release.py +0 -45
  70. winipedia_utils/git/pre_commit/hooks.py +0 -147
  71. winipedia_utils/projects/poetry/poetry.py +0 -129
  72. winipedia_utils/testing/__init__.py +0 -1
  73. winipedia_utils/testing/tests/__init__.py +0 -1
  74. winipedia_utils/testing/tests/base/__init__.py +0 -1
  75. winipedia_utils/testing/tests/base/fixtures/__init__.py +0 -1
  76. winipedia_utils/testing/tests/base/fixtures/scopes/__init__.py +0 -1
  77. winipedia_utils/testing/tests/base/utils/__init__.py +0 -1
  78. winipedia_utils-0.4.43.dist-info/RECORD +0 -94
  79. /winipedia_utils/{data/dataframe → artifacts}/__init__.py +0 -0
  80. /winipedia_utils/{data/structures → dev}/__init__.py +0 -0
  81. /winipedia_utils/{git/github → dev/artifacts}/__init__.py +0 -0
  82. /winipedia_utils/{git/github/repo → dev/configs}/__init__.py +0 -0
  83. /winipedia_utils/{git/github/workflows → dev/configs}/base/__init__.py +0 -0
  84. /winipedia_utils/{git/github → dev/configs}/workflows/__init__.py +0 -0
  85. /winipedia_utils/{resources → dev/configs/workflows/base}/__init__.py +0 -0
  86. /winipedia_utils/{git → dev/git}/__init__.py +0 -0
  87. /winipedia_utils/{resources/svgs → dev/git/github}/__init__.py +0 -0
  88. /winipedia_utils/{git → dev/git}/pre_commit/__init__.py +0 -0
  89. /winipedia_utils/{projects → dev/projects}/__init__.py +0 -0
  90. /winipedia_utils/{projects → dev/projects}/poetry/__init__.py +0 -0
  91. /winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/function.py +0 -0
  92. /winipedia_utils/{testing → dev/testing}/tests/base/fixtures/scopes/package.py +0 -0
  93. /winipedia_utils/{data → utils/data}/__init__.py +0 -0
  94. /winipedia_utils/{data → utils/data}/structures/dicts.py +0 -0
  95. /winipedia_utils/{text → utils/data/structures/text}/__init__.py +0 -0
  96. /winipedia_utils/{git → utils/git}/gitignore/__init__.py +0 -0
  97. /winipedia_utils/{iterating → utils/iterating}/__init__.py +0 -0
  98. /winipedia_utils/{concurrent → utils/iterating/concurrent}/__init__.py +0 -0
  99. /winipedia_utils/{iterating → utils/iterating}/iterate.py +0 -0
  100. /winipedia_utils/{logging → utils/logging}/__init__.py +0 -0
  101. /winipedia_utils/{logging → utils/logging}/ansi.py +0 -0
  102. /winipedia_utils/{logging → utils/logging}/config.py +0 -0
  103. /winipedia_utils/{modules → utils/modules}/__init__.py +0 -0
  104. /winipedia_utils/{oop → utils/oop}/__init__.py +0 -0
  105. /winipedia_utils/{oop → utils/oop}/mixins/__init__.py +0 -0
  106. /winipedia_utils/{os → utils/os}/__init__.py +0 -0
  107. /winipedia_utils/{resources → utils/resources}/svgs/delete_garbage_can.svg +0 -0
  108. /winipedia_utils/{resources → utils/resources}/svgs/download_arrow.svg +0 -0
  109. /winipedia_utils/{resources → utils/resources}/svgs/exit_fullscreen_icon.svg +0 -0
  110. /winipedia_utils/{resources → utils/resources}/svgs/fullscreen_icon.svg +0 -0
  111. /winipedia_utils/{resources → utils/resources}/svgs/menu_icon.svg +0 -0
  112. /winipedia_utils/{resources → utils/resources}/svgs/pause_icon.svg +0 -0
  113. /winipedia_utils/{resources → utils/resources}/svgs/play_icon.svg +0 -0
  114. /winipedia_utils/{resources → utils/resources}/svgs/plus_icon.svg +0 -0
  115. /winipedia_utils/{security → utils/security}/__init__.py +0 -0
  116. /winipedia_utils/{security → utils/security}/cryptography.py +0 -0
  117. /winipedia_utils/{security → utils/security}/keyring.py +0 -0
  118. /winipedia_utils/{testing → utils/testing}/fixtures.py +0 -0
  119. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/WHEEL +0 -0
  120. {winipedia_utils-0.4.43.dist-info → winipedia_utils-0.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,27 @@
1
+ """Build utilities for creating and managing project builds.
2
+
3
+ This module provides functions for building and managing project artifacts,
4
+ including creating build scripts, configuring build environments, and
5
+ handling build dependencies. These utilities help with the packaging and
6
+ distribution of project code.
7
+ """
8
+
9
+ from pathlib import Path
10
+
11
+ from winipedia_utils.dev.artifacts.build import Build
12
+
13
+
14
+ class WinipediaUtilsBuild(Build):
15
+ """Build script for winipedia_utils."""
16
+
17
+ @classmethod
18
+ def get_artifacts(cls) -> list[Path]:
19
+ """Build the project."""
20
+ paths = [cls.ARTIFACTS_PATH / "build.txt"]
21
+ for path in paths:
22
+ path.write_text("Hello World!")
23
+ return paths
24
+
25
+
26
+ if __name__ == "__main__":
27
+ WinipediaUtilsBuild()
@@ -0,0 +1,62 @@
1
+ """Build utilities for creating and managing project builds.
2
+
3
+ This module provides functions for building and managing project artifacts,
4
+ including creating build scripts, configuring build environments, and
5
+ handling build dependencies. These utilities help with the packaging and
6
+ distribution of project code.
7
+ """
8
+
9
+ import platform
10
+ from abc import abstractmethod
11
+ from pathlib import Path
12
+
13
+ from winipedia_utils.dev.configs.workflows.base.base import Workflow
14
+ from winipedia_utils.utils.oop.mixins.mixin import ABCLoggingMixin
15
+
16
+
17
+ class Build(ABCLoggingMixin):
18
+ """Base class for build scripts.
19
+
20
+ Subclass this class and implement the get_artifacts method to create
21
+ a build script for your project. The build method will be called
22
+ automatically when the class is initialized. At the end of the file add
23
+ if __name__ == "__main__":
24
+ YourBuildClass()
25
+ """
26
+
27
+ ARTIFACTS_PATH = Workflow.ARTIFACTS_PATH
28
+
29
+ @classmethod
30
+ @abstractmethod
31
+ def get_artifacts(cls) -> list[Path]:
32
+ """Build the project.
33
+
34
+ Returns:
35
+ list[Path]: List of paths to the built artifacts
36
+ """
37
+
38
+ @classmethod
39
+ def __init__(cls) -> None:
40
+ """Initialize the build script."""
41
+ cls.build()
42
+
43
+ @classmethod
44
+ def build(cls) -> None:
45
+ """Build the project.
46
+
47
+ This method is called by the __init__ method.
48
+ It takes all the files and renames them with -platform.system()
49
+ and puts them in the artifacts folder.
50
+ """
51
+ cls.ARTIFACTS_PATH.mkdir(parents=True, exist_ok=True)
52
+ artifacts = cls.get_artifacts()
53
+ for artifact in artifacts:
54
+ parent = artifact.parent
55
+ if parent != cls.ARTIFACTS_PATH:
56
+ msg = f"You must create {artifact} in {cls.ARTIFACTS_PATH}"
57
+ raise FileNotFoundError(msg)
58
+
59
+ # rename the files with -platform.system()
60
+ new_name = f"{artifact.stem}-{platform.system()}{artifact.suffix}"
61
+ new_path = cls.ARTIFACTS_PATH / new_name
62
+ artifact.rename(new_path)
@@ -9,14 +9,13 @@ import tomlkit
9
9
  import yaml
10
10
  from dotenv import dotenv_values
11
11
 
12
- import winipedia_utils
13
- from winipedia_utils.iterating.iterate import nested_structure_is_subset
14
- from winipedia_utils.modules.class_ import init_all_nonabstract_subclasses
15
- from winipedia_utils.modules.package import get_src_package
16
- from winipedia_utils.projects.poetry.poetry import (
17
- get_python_module_script,
12
+ from winipedia_utils.dev.projects.poetry.poetry import (
13
+ get_poetry_run_module_script,
18
14
  )
19
- from winipedia_utils.text.string import split_on_uppercase
15
+ from winipedia_utils.utils.data.structures.text.string import split_on_uppercase
16
+ from winipedia_utils.utils.iterating.iterate import nested_structure_is_subset
17
+ from winipedia_utils.utils.modules.class_ import init_all_nonabstract_subclasses
18
+ from winipedia_utils.utils.modules.package import DependencyGraph, get_src_package
20
19
 
21
20
 
22
21
  class ConfigFile(ABC):
@@ -149,15 +148,21 @@ class ConfigFile(ABC):
149
148
  @classmethod
150
149
  def init_config_files(cls) -> None:
151
150
  """Initialize all subclasses."""
152
- init_all_nonabstract_subclasses(cls, load_package_before=winipedia_utils)
153
- init_all_nonabstract_subclasses(cls, load_package_before=get_src_package())
151
+ pkgs_depending_on_winipedia_utils = (
152
+ DependencyGraph().get_all_depending_on_winipedia_utils(
153
+ include_winipedia_utils=True
154
+ )
155
+ )
156
+ pkgs_depending_on_winipedia_utils.add(get_src_package())
157
+ for pkg in pkgs_depending_on_winipedia_utils:
158
+ init_all_nonabstract_subclasses(cls, load_package_before=pkg)
154
159
 
155
160
  @staticmethod
156
- def get_python_setup_script() -> str:
161
+ def get_poetry_run_setup_script() -> str:
157
162
  """Get the poetry run setup script."""
158
163
  from winipedia_utils import setup # noqa: PLC0415 # avoid circular import
159
164
 
160
- return get_python_module_script(setup)
165
+ return get_poetry_run_module_script(setup)
161
166
 
162
167
 
163
168
  class YamlConfigFile(ConfigFile):
@@ -5,8 +5,8 @@ from typing import Any
5
5
 
6
6
  import requests
7
7
 
8
- from winipedia_utils.testing.config import ExperimentConfigFile
9
- from winipedia_utils.text.config import ConfigFile, DotEnvConfigFile
8
+ from winipedia_utils.dev.configs.base.config import ConfigFile, DotEnvConfigFile
9
+ from winipedia_utils.dev.configs.testing import ExperimentConfigFile
10
10
 
11
11
 
12
12
  class GitIgnoreConfigFile(ConfigFile):
@@ -4,10 +4,10 @@ from pathlib import Path
4
4
  from typing import Any
5
5
 
6
6
  import winipedia_utils
7
- from winipedia_utils.logging.logger import get_logger
8
- from winipedia_utils.modules.package import make_name_from_package
9
- from winipedia_utils.os.os import run_subprocess
10
- from winipedia_utils.text.config import YamlConfigFile
7
+ from winipedia_utils.dev.configs.base.config import YamlConfigFile
8
+ from winipedia_utils.utils.data.structures.text.string import make_name_from_obj
9
+ from winipedia_utils.utils.logging.logger import get_logger
10
+ from winipedia_utils.utils.os.os import run_subprocess
11
11
 
12
12
  logger = get_logger(__name__)
13
13
 
@@ -29,7 +29,7 @@ class PreCommitConfigConfigFile(YamlConfigFile):
29
29
  @classmethod
30
30
  def get_configs(cls) -> dict[str, Any]:
31
31
  """Get the config."""
32
- hook_name = make_name_from_package(winipedia_utils, capitalize=False)
32
+ hook_name = make_name_from_obj(winipedia_utils, capitalize=False)
33
33
  return {
34
34
  "repos": [
35
35
  {
@@ -38,7 +38,7 @@ class PreCommitConfigConfigFile(YamlConfigFile):
38
38
  {
39
39
  "id": hook_name,
40
40
  "name": hook_name,
41
- "entry": cls.get_python_setup_script(),
41
+ "entry": cls.get_poetry_run_setup_script(),
42
42
  "language": "system",
43
43
  "always_run": True,
44
44
  "pass_filenames": False,
@@ -1,30 +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
- from winipedia_utils.modules.package import get_src_package, make_name_from_package
7
- from winipedia_utils.projects.poetry.poetry import VersionConstraint
8
- from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
9
- from winipedia_utils.text.config import ConfigFile, TomlConfigFile
7
+ import requests
8
+ from packaging.version import Version
9
+
10
+ from winipedia_utils.dev.configs.base.config import ConfigFile, TomlConfigFile
11
+ from winipedia_utils.dev.configs.testing import ExperimentConfigFile
12
+ from winipedia_utils.dev.projects.poetry.dev_deps import DEV_DEPENDENCIES
13
+ from winipedia_utils.dev.projects.poetry.poetry import POETRY_ARG, VersionConstraint
14
+ from winipedia_utils.dev.testing.convention import TESTS_PACKAGE_NAME
15
+ from winipedia_utils.utils.data.structures.text.string import make_name_from_obj
16
+ from winipedia_utils.utils.os.os import run_subprocess
10
17
 
11
18
 
12
19
  class PyprojectConfigFile(TomlConfigFile):
13
20
  """Config file for pyproject.toml."""
14
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
+
15
37
  @classmethod
16
38
  def get_parent_path(cls) -> Path:
17
39
  """Get the path to the config file."""
18
40
  return Path()
19
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
+
20
52
  @classmethod
21
53
  def get_configs(cls) -> dict[str, Any]:
22
54
  """Get the config."""
23
55
  return {
24
56
  "project": {
25
- "name": make_name_from_package(get_src_package(), capitalize=False),
57
+ "name": make_name_from_obj(cls.get_repository_name(), capitalize=False),
26
58
  "readme": "README.md",
27
- "dynamic": ["dependencies"],
59
+ "dependencies": list(cls.get_dependencies()),
28
60
  },
29
61
  "build-system": {
30
62
  "requires": ["poetry-core>=2.0.0,<3.0.0"],
@@ -32,15 +64,11 @@ class PyprojectConfigFile(TomlConfigFile):
32
64
  },
33
65
  "tool": {
34
66
  "poetry": {
35
- "packages": [{"include": get_src_package().__name__}],
36
- "dependencies": dict.fromkeys(
37
- cls.get_dependencies(),
38
- "*",
39
- ),
67
+ "packages": [{"include": cls.get_repository_name()}],
40
68
  "group": {
41
69
  "dev": {
42
70
  "dependencies": dict.fromkeys(
43
- cls.get_dev_dependencies(),
71
+ cls.get_dev_dependencies() | DEV_DEPENDENCIES,
44
72
  "*",
45
73
  )
46
74
  }
@@ -62,7 +90,9 @@ class PyprojectConfigFile(TomlConfigFile):
62
90
  "files": ".",
63
91
  },
64
92
  "pytest": {"ini_options": {"testpaths": [TESTS_PACKAGE_NAME]}},
65
- "bandit": {},
93
+ "bandit": {
94
+ "exclude_dirs": [ExperimentConfigFile.get_path().as_posix()],
95
+ },
66
96
  },
67
97
  }
68
98
 
@@ -73,6 +103,29 @@ class PyprojectConfigFile(TomlConfigFile):
73
103
  package_name = str(project_dict.get("name", ""))
74
104
  return package_name.replace("-", "_")
75
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
+
76
129
  @classmethod
77
130
  def get_all_dependencies(cls) -> set[str]:
78
131
  """Get all dependencies."""
@@ -100,9 +153,17 @@ class PyprojectConfigFile(TomlConfigFile):
100
153
  @classmethod
101
154
  def get_dependencies(cls) -> set[str]:
102
155
  """Get the dependencies."""
103
- return set(
104
- cls.load().get("tool", {}).get("poetry", {}).get("dependencies", {}).keys()
105
- )
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
106
167
 
107
168
  @classmethod
108
169
  def get_expected_dev_dependencies(cls) -> set[str]:
@@ -132,25 +193,29 @@ class PyprojectConfigFile(TomlConfigFile):
132
193
  return cls.get_main_author()["name"]
133
194
 
134
195
  @classmethod
135
- def get_latest_possible_python_version(cls) -> str:
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:
136
209
  """Get the latest possible python version."""
137
210
  constraint = cls.load()["project"]["requires-python"]
138
211
  version_constraint = VersionConstraint(constraint)
139
- upper = version_constraint.get_upper_exclusive()
140
- if upper is None:
141
- return "3.x"
142
-
143
- # convert to inclusive
144
- if upper.micro != 0:
145
- micro = upper.micro - 1
146
- return f"{upper.major}.{upper.minor}" + (f".{micro}" if micro != 0 else "")
147
- if upper.minor != 0:
148
- minor = upper.minor - 1
149
- return f"{upper.major}" + (f".{minor}" if minor != 0 else "")
150
- 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
151
216
 
152
217
  @classmethod
153
- def get_first_supported_python_version(cls) -> str:
218
+ def get_first_supported_python_version(cls) -> Version:
154
219
  """Get the first supported python version."""
155
220
  constraint = cls.load()["project"]["requires-python"]
156
221
  version_constraint = VersionConstraint(constraint)
@@ -158,7 +223,28 @@ class PyprojectConfigFile(TomlConfigFile):
158
223
  if lower is None:
159
224
  msg = "Need a lower bound for python version"
160
225
  raise ValueError(msg)
161
- return str(lower)
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)
162
248
 
163
249
 
164
250
  class TypedConfigFile(ConfigFile):
@@ -220,7 +306,9 @@ class DotPythonVersionConfigFile(ConfigFile):
220
306
  def get_configs(cls) -> dict[str, Any]:
221
307
  """Get the config."""
222
308
  return {
223
- cls.VERSION_KEY: PyprojectConfigFile.get_first_supported_python_version()
309
+ cls.VERSION_KEY: str(
310
+ PyprojectConfigFile.get_first_supported_python_version()
311
+ )
224
312
  }
225
313
 
226
314
  @classmethod
@@ -4,8 +4,9 @@ from abc import abstractmethod
4
4
  from pathlib import Path
5
5
  from typing import Any
6
6
 
7
- from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
8
- from winipedia_utils.text.config import ConfigFile
7
+ from winipedia_utils.dev.configs.base.config import ConfigFile
8
+ from winipedia_utils.dev.testing.convention import TESTS_PACKAGE_NAME
9
+ from winipedia_utils.utils.modules.module import make_obj_importpath
9
10
 
10
11
 
11
12
  class PythonConfigFile(ConfigFile):
@@ -73,7 +74,9 @@ class ConftestConfigFile(PythonTestsConfigFile):
73
74
  @classmethod
74
75
  def get_content_str(cls) -> str:
75
76
  """Get the config content."""
76
- return '''"""Pytest configuration for tests.
77
+ from winipedia_utils.dev.testing.tests import conftest # noqa: PLC0415
78
+
79
+ return f'''"""Pytest configuration for tests.
77
80
 
78
81
  This module configures pytest plugins for the test suite, setting up the necessary
79
82
  fixtures and hooks for the different
@@ -82,7 +85,7 @@ It also import custom plugins from tests/base/scopes.
82
85
  This file should not be modified manually.
83
86
  """
84
87
 
85
- pytest_plugins = ["winipedia_utils.testing.tests.conftest"]
88
+ pytest_plugins = ["{make_obj_importpath(conftest)}"]
86
89
  '''
87
90
 
88
91