winipedia-utils 0.2.58__py3-none-any.whl → 0.3.7__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/concurrent/concurrent.py +7 -2
- winipedia_utils/concurrent/multiprocessing.py +1 -2
- winipedia_utils/concurrent/multithreading.py +2 -2
- winipedia_utils/git/gitignore/config.py +54 -0
- winipedia_utils/git/gitignore/gitignore.py +5 -63
- winipedia_utils/git/pre_commit/config.py +45 -58
- winipedia_utils/git/pre_commit/hooks.py +13 -13
- winipedia_utils/git/pre_commit/run_hooks.py +2 -2
- winipedia_utils/git/workflows/base/base.py +102 -52
- winipedia_utils/git/workflows/publish.py +25 -49
- winipedia_utils/git/workflows/release.py +22 -46
- winipedia_utils/iterating/iterate.py +59 -1
- winipedia_utils/modules/class_.py +60 -10
- winipedia_utils/modules/function.py +18 -1
- winipedia_utils/modules/package.py +16 -7
- winipedia_utils/projects/poetry/config.py +122 -110
- winipedia_utils/projects/poetry/poetry.py +1 -8
- winipedia_utils/projects/project.py +7 -13
- winipedia_utils/setup.py +11 -29
- winipedia_utils/testing/config.py +95 -0
- winipedia_utils/testing/create_tests.py +4 -18
- winipedia_utils/testing/skip.py +10 -0
- winipedia_utils/testing/tests/base/fixtures/scopes/class_.py +3 -3
- winipedia_utils/testing/tests/base/fixtures/scopes/module.py +6 -4
- winipedia_utils/testing/tests/base/fixtures/scopes/session.py +28 -176
- winipedia_utils/testing/tests/base/utils/utils.py +11 -55
- winipedia_utils/text/config.py +143 -0
- winipedia_utils-0.3.7.dist-info/METADATA +324 -0
- {winipedia_utils-0.2.58.dist-info → winipedia_utils-0.3.7.dist-info}/RECORD +31 -28
- winipedia_utils/consts.py +0 -21
- winipedia_utils-0.2.58.dist-info/METADATA +0 -738
- {winipedia_utils-0.2.58.dist-info → winipedia_utils-0.3.7.dist-info}/WHEEL +0 -0
- {winipedia_utils-0.2.58.dist-info → winipedia_utils-0.3.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,26 +9,14 @@ through pytest's autouse mechanism.
|
|
|
9
9
|
from importlib import import_module
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
from winipedia_utils.git.gitignore.gitignore import _gitignore_is_correct
|
|
14
|
-
from winipedia_utils.git.pre_commit.config import (
|
|
15
|
-
_pre_commit_config_is_correct,
|
|
16
|
-
)
|
|
17
|
-
from winipedia_utils.git.workflows.publish import (
|
|
18
|
-
PUBLISH_WORKFLOW_PATH,
|
|
19
|
-
_publish_config_is_correct,
|
|
20
|
-
)
|
|
21
|
-
from winipedia_utils.git.workflows.release import _release_config_is_correct
|
|
22
|
-
from winipedia_utils.modules.module import to_path
|
|
12
|
+
import winipedia_utils
|
|
23
13
|
from winipedia_utils.modules.package import (
|
|
24
14
|
find_packages,
|
|
25
15
|
get_src_package,
|
|
26
16
|
walk_package,
|
|
27
17
|
)
|
|
28
18
|
from winipedia_utils.projects.poetry.config import (
|
|
29
|
-
|
|
30
|
-
get_dev_dependencies_from_pyproject_toml,
|
|
31
|
-
get_poetry_package_name,
|
|
19
|
+
PyProjectTomlConfig,
|
|
32
20
|
)
|
|
33
21
|
from winipedia_utils.testing.assertions import assert_with_msg
|
|
34
22
|
from winipedia_utils.testing.convention import (
|
|
@@ -36,13 +24,11 @@ from winipedia_utils.testing.convention import (
|
|
|
36
24
|
make_test_obj_importpath_from_obj,
|
|
37
25
|
)
|
|
38
26
|
from winipedia_utils.testing.fixtures import autouse_session_fixture
|
|
39
|
-
from winipedia_utils.
|
|
40
|
-
_conftest_content_is_correct,
|
|
41
|
-
)
|
|
27
|
+
from winipedia_utils.text.config import ConfigFile
|
|
42
28
|
|
|
43
29
|
|
|
44
30
|
@autouse_session_fixture
|
|
45
|
-
def
|
|
31
|
+
def assert_dev_dependencies_config_is_correct() -> None:
|
|
46
32
|
"""Verify that the dev dependencies in consts.py are correct.
|
|
47
33
|
|
|
48
34
|
This fixture runs once per test session and checks that the dev dependencies
|
|
@@ -53,19 +39,23 @@ def _test_dev_dependencies_const_correct() -> None:
|
|
|
53
39
|
AssertionError: If the dev dependencies in consts.py are not correct
|
|
54
40
|
|
|
55
41
|
"""
|
|
56
|
-
|
|
42
|
+
config = PyProjectTomlConfig()
|
|
43
|
+
if config.get_package_name() != winipedia_utils.__name__:
|
|
57
44
|
# this const is only used in winipedia_utils
|
|
58
45
|
# to be able to install them with setup.py
|
|
59
46
|
return
|
|
60
|
-
actual_dev_dependencies =
|
|
47
|
+
actual_dev_dependencies = config.get_dev_dependencies()
|
|
48
|
+
expected_dev_dependencies = config.get_configs()["tool"]["poetry"]["group"]["dev"][
|
|
49
|
+
"dependencies"
|
|
50
|
+
].keys()
|
|
61
51
|
assert_with_msg(
|
|
62
|
-
set(actual_dev_dependencies) == set(
|
|
52
|
+
set(actual_dev_dependencies) == set(expected_dev_dependencies),
|
|
63
53
|
"Dev dependencies in consts.py are not correct",
|
|
64
54
|
)
|
|
65
55
|
|
|
66
56
|
|
|
67
57
|
@autouse_session_fixture
|
|
68
|
-
def
|
|
58
|
+
def assert_config_files_are_correct() -> None:
|
|
69
59
|
"""Verify that the dev dependencies are installed.
|
|
70
60
|
|
|
71
61
|
This fixture runs once per test session and checks that the dev dependencies
|
|
@@ -75,131 +65,12 @@ def _test_dev_dependencies_are_in_pyproject_toml() -> None:
|
|
|
75
65
|
ImportError: If a dev dependency is not installed
|
|
76
66
|
|
|
77
67
|
"""
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
set(_DEV_DEPENDENCIES).issubset(set(dev_dependencies)),
|
|
81
|
-
"Dev dependencies in consts.py are not a subset of the ones in pyproject.toml",
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
@autouse_session_fixture
|
|
86
|
-
def _test_conftest_exists_and_is_correct() -> None:
|
|
87
|
-
"""Verify that the conftest.py file exists and has the correct content.
|
|
88
|
-
|
|
89
|
-
This fixture runs once per test session and checks that the conftest.py file
|
|
90
|
-
exists in the tests directory and contains the correct pytest_plugins configuration.
|
|
91
|
-
|
|
92
|
-
Raises:
|
|
93
|
-
AssertionError: If the conftest.py file doesn't exist or has incorrect content
|
|
94
|
-
|
|
95
|
-
"""
|
|
96
|
-
conftest_path = Path(TESTS_PACKAGE_NAME, "conftest.py")
|
|
97
|
-
assert_with_msg(
|
|
98
|
-
conftest_path.is_file(),
|
|
99
|
-
f"Expected conftest.py file at {conftest_path} but it doesn't exist",
|
|
100
|
-
)
|
|
101
|
-
|
|
102
|
-
assert_with_msg(
|
|
103
|
-
_conftest_content_is_correct(conftest_path),
|
|
104
|
-
"conftest.py has incorrect content",
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@autouse_session_fixture
|
|
109
|
-
def _test_pyproject_toml_is_correct() -> None:
|
|
110
|
-
"""Verify that the pyproject.toml file exists and has the correct content.
|
|
111
|
-
|
|
112
|
-
This fixture runs once per test session and checks that the pyproject.toml file
|
|
113
|
-
exists in the root directory and contains the correct content.
|
|
114
|
-
|
|
115
|
-
Raises:
|
|
116
|
-
AssertionError: If the pyproject.toml file doesn't exist
|
|
117
|
-
or has incorrect content
|
|
118
|
-
|
|
119
|
-
"""
|
|
120
|
-
pyproject_toml_path = Path("pyproject.toml")
|
|
121
|
-
assert_with_msg(
|
|
122
|
-
pyproject_toml_path.is_file(),
|
|
123
|
-
f"Expected pyproject.toml file at {pyproject_toml_path} but it doesn't exist",
|
|
124
|
-
)
|
|
125
|
-
assert_with_msg(
|
|
126
|
-
_pyproject_tool_configs_are_correct(),
|
|
127
|
-
"pyproject.toml has incorrect content.",
|
|
128
|
-
)
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
@autouse_session_fixture
|
|
132
|
-
def _test_pre_commit_config_yaml_is_correct() -> None:
|
|
133
|
-
"""Verify that the pre-commit yaml is correctly defining winipedia utils hook.
|
|
134
|
-
|
|
135
|
-
Checks that the yaml starts with the winipedia utils hook.
|
|
136
|
-
"""
|
|
137
|
-
pre_commit_config = Path(".pre-commit-config.yaml")
|
|
138
|
-
|
|
139
|
-
assert_with_msg(
|
|
140
|
-
pre_commit_config.is_file(),
|
|
141
|
-
f"Expected {pre_commit_config} to exist but it doesn't.",
|
|
142
|
-
)
|
|
143
|
-
assert_with_msg(
|
|
144
|
-
_pre_commit_config_is_correct(),
|
|
145
|
-
"Pre commit config is not correct.",
|
|
146
|
-
)
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
@autouse_session_fixture
|
|
150
|
-
def _test_gitignore_is_correct() -> None:
|
|
151
|
-
"""Verify that the .gitignore file exists and has the correct content.
|
|
152
|
-
|
|
153
|
-
This fixture runs once per test session and checks that the .gitignore file
|
|
154
|
-
exists in the root directory and contains the correct content.
|
|
155
|
-
|
|
156
|
-
Raises:
|
|
157
|
-
AssertionError: If the .gitignore file doesn't exist
|
|
158
|
-
or has incorrect content
|
|
159
|
-
|
|
160
|
-
"""
|
|
161
|
-
gitignore_path = Path(".gitignore")
|
|
162
|
-
assert_with_msg(
|
|
163
|
-
gitignore_path.is_file(),
|
|
164
|
-
f"Expected {gitignore_path} to exist but it doesn't.",
|
|
165
|
-
)
|
|
166
|
-
assert_with_msg(
|
|
167
|
-
_gitignore_is_correct(),
|
|
168
|
-
"Gitignore is not correct.",
|
|
169
|
-
)
|
|
68
|
+
# subclasses of ConfigFile
|
|
69
|
+
ConfigFile.init_config_files()
|
|
170
70
|
|
|
171
71
|
|
|
172
72
|
@autouse_session_fixture
|
|
173
|
-
def
|
|
174
|
-
"""Verify that the publish workflow is correctly defined.
|
|
175
|
-
|
|
176
|
-
If the file does not exist, we skip this test bc not all projects necessarily
|
|
177
|
-
need to publish to pypi, e.g. they are binaries or private usage only or for profit.
|
|
178
|
-
"""
|
|
179
|
-
path = PUBLISH_WORKFLOW_PATH
|
|
180
|
-
# if folder exists but the file not then we skip this test
|
|
181
|
-
if path.parent.exists() and not path.exists():
|
|
182
|
-
return
|
|
183
|
-
assert_with_msg(
|
|
184
|
-
_publish_config_is_correct(),
|
|
185
|
-
"Publish workflow is not correct.",
|
|
186
|
-
)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
@autouse_session_fixture
|
|
190
|
-
def _test_release_workflow_is_correct() -> None:
|
|
191
|
-
"""Verify that the release workflow is correctly defined.
|
|
192
|
-
|
|
193
|
-
This workflow is mandatory for all projects.
|
|
194
|
-
"""
|
|
195
|
-
assert_with_msg(
|
|
196
|
-
_release_config_is_correct(),
|
|
197
|
-
"Release workflow is not correct.",
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
@autouse_session_fixture
|
|
202
|
-
def _test_no_namespace_packages() -> None:
|
|
73
|
+
def assert_no_namespace_packages() -> None:
|
|
203
74
|
"""Verify that there are no namespace packages in the project.
|
|
204
75
|
|
|
205
76
|
This fixture runs once per test session and checks that all packages in the
|
|
@@ -221,7 +92,7 @@ def _test_no_namespace_packages() -> None:
|
|
|
221
92
|
|
|
222
93
|
|
|
223
94
|
@autouse_session_fixture
|
|
224
|
-
def
|
|
95
|
+
def assert_all_src_code_in_one_package() -> None:
|
|
225
96
|
"""Verify that all source code is in a single package.
|
|
226
97
|
|
|
227
98
|
This fixture runs once per test session and checks that there is only one
|
|
@@ -241,7 +112,7 @@ def _test_all_src_code_in_one_package() -> None:
|
|
|
241
112
|
|
|
242
113
|
|
|
243
114
|
@autouse_session_fixture
|
|
244
|
-
def
|
|
115
|
+
def assert_src_package_correctly_named() -> None:
|
|
245
116
|
"""Verify that the source package is correctly named.
|
|
246
117
|
|
|
247
118
|
This fixture runs once per test session and checks that the source package
|
|
@@ -252,34 +123,17 @@ def _test_src_package_correctly_named() -> None:
|
|
|
252
123
|
|
|
253
124
|
"""
|
|
254
125
|
src_package = get_src_package().__name__
|
|
126
|
+
config = PyProjectTomlConfig()
|
|
127
|
+
expected_package = config.get_package_name()
|
|
255
128
|
assert_with_msg(
|
|
256
|
-
src_package ==
|
|
257
|
-
f"Expected source package to be named {
|
|
129
|
+
src_package == expected_package,
|
|
130
|
+
f"Expected source package to be named {expected_package}, "
|
|
258
131
|
f"but it is named {src_package}",
|
|
259
132
|
)
|
|
260
133
|
|
|
261
134
|
|
|
262
135
|
@autouse_session_fixture
|
|
263
|
-
def
|
|
264
|
-
"""Verify that the py.typed file exists in the source package.
|
|
265
|
-
|
|
266
|
-
This fixture runs once per test session and checks that the py.typed file
|
|
267
|
-
exists in the source package.
|
|
268
|
-
|
|
269
|
-
Raises:
|
|
270
|
-
AssertionError: If the py.typed file doesn't exist
|
|
271
|
-
|
|
272
|
-
"""
|
|
273
|
-
src_package = get_src_package()
|
|
274
|
-
py_typed_path = to_path(src_package.__name__, is_package=True) / "py.typed"
|
|
275
|
-
assert_with_msg(
|
|
276
|
-
py_typed_path.exists(),
|
|
277
|
-
f"Expected py.typed file to exist at {py_typed_path}",
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
@autouse_session_fixture
|
|
282
|
-
def _test_project_structure_mirrored() -> None:
|
|
136
|
+
def assert_project_structure_mirrored() -> None:
|
|
283
137
|
"""Verify that the project structure is mirrored in tests.
|
|
284
138
|
|
|
285
139
|
This fixture runs once per test session and checks that for every package and
|
|
@@ -311,20 +165,18 @@ def _test_project_structure_mirrored() -> None:
|
|
|
311
165
|
|
|
312
166
|
|
|
313
167
|
@autouse_session_fixture
|
|
314
|
-
def
|
|
315
|
-
"""Verify that the
|
|
168
|
+
def assert_no_unit_test_package_usage() -> None:
|
|
169
|
+
"""Verify that the unit test package is not used in the project.
|
|
316
170
|
|
|
317
|
-
This fixture runs once per test session and checks that the
|
|
171
|
+
This fixture runs once per test session and checks that the unit test package
|
|
318
172
|
is not used in the project.
|
|
319
173
|
|
|
320
174
|
Raises:
|
|
321
|
-
AssertionError: If the
|
|
175
|
+
AssertionError: If the unit test package is used
|
|
322
176
|
|
|
323
177
|
"""
|
|
324
178
|
for path in Path().rglob("*.py"):
|
|
325
|
-
if path == to_path(__name__, is_package=False):
|
|
326
|
-
continue
|
|
327
179
|
assert_with_msg(
|
|
328
|
-
"
|
|
329
|
-
f"Found
|
|
180
|
+
"UnitTest".lower() not in path.read_text(encoding="utf-8"),
|
|
181
|
+
f"Found unit test package usage in {path}. Use pytest instead.",
|
|
330
182
|
)
|
|
@@ -10,10 +10,10 @@ Returns:
|
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
12
|
from collections.abc import Callable
|
|
13
|
-
from pathlib import Path
|
|
14
13
|
from types import ModuleType
|
|
15
14
|
from typing import Any
|
|
16
15
|
|
|
16
|
+
from winipedia_utils.modules.function import is_abstractmethod
|
|
17
17
|
from winipedia_utils.modules.module import get_objs_from_obj, make_obj_importpath
|
|
18
18
|
from winipedia_utils.testing.assertions import assert_with_msg
|
|
19
19
|
from winipedia_utils.testing.convention import (
|
|
@@ -23,7 +23,7 @@ from winipedia_utils.testing.convention import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def assert_no_untested_objs(
|
|
27
27
|
test_obj: ModuleType | type | Callable[..., Any],
|
|
28
28
|
) -> None:
|
|
29
29
|
"""Assert that all objects in the source have corresponding test objects.
|
|
@@ -51,61 +51,17 @@ def _assert_no_untested_objs(
|
|
|
51
51
|
assert_with_msg(not untested_objs, make_untested_summary_error_msg(untested_objs))
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
def
|
|
55
|
-
"""
|
|
56
|
-
return '''
|
|
57
|
-
"""Pytest configuration for tests.
|
|
58
|
-
|
|
59
|
-
This module configures pytest plugins for the test suite, setting up the necessary
|
|
60
|
-
fixtures and hooks for the different
|
|
61
|
-
test scopes (function, class, module, package, session).
|
|
62
|
-
It also import custom plugins from tests/base/scopes.
|
|
63
|
-
This file should not be modified manually.
|
|
64
|
-
"""
|
|
65
|
-
|
|
66
|
-
pytest_plugins = ["winipedia_utils.testing.tests.conftest"]
|
|
67
|
-
'''.strip()
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def _conftest_content_is_correct(conftest_path: Path) -> bool:
|
|
71
|
-
"""Check if the conftest.py file has the correct content.
|
|
54
|
+
def assert_isabstrct_method(method: Any) -> None:
|
|
55
|
+
"""Assert that a method is an abstract method.
|
|
72
56
|
|
|
73
57
|
Args:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
True if the conftest.py file exists and has the correct content, False otherwise
|
|
78
|
-
|
|
79
|
-
"""
|
|
80
|
-
if not conftest_path.exists():
|
|
81
|
-
return False
|
|
82
|
-
return conftest_path.read_text().startswith(_get_conftest_content())
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def _get_test_0_content() -> str:
|
|
86
|
-
"""Get the content for a test_0.py file when using winipedia_utils."""
|
|
87
|
-
return '''
|
|
88
|
-
"""Contains an empty test."""
|
|
58
|
+
method: The method to check
|
|
89
59
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"""Empty test.
|
|
93
|
-
|
|
94
|
-
Exists so that when no tests are written yet the base fixtures are executed.
|
|
95
|
-
"""
|
|
96
|
-
'''.strip()
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _test_0_content_is_correct(test_0_path: Path) -> bool:
|
|
100
|
-
"""Check if the test_0.py file has the correct content.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
test_0_path: The path to the test_0.py file
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
True if the test_0.py file exists and has the correct content, False otherwise
|
|
60
|
+
Raises:
|
|
61
|
+
AssertionError: If the method is not an abstract method
|
|
107
62
|
|
|
108
63
|
"""
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
64
|
+
assert_with_msg(
|
|
65
|
+
is_abstractmethod(method),
|
|
66
|
+
f"Expected {method} to be abstract method",
|
|
67
|
+
)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Base class for config files."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import tomlkit
|
|
8
|
+
import yaml
|
|
9
|
+
|
|
10
|
+
import winipedia_utils
|
|
11
|
+
from winipedia_utils.iterating.iterate import nested_structure_is_subset
|
|
12
|
+
from winipedia_utils.modules.class_ import init_all_nonabstract_subclasses
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConfigFile(ABC):
|
|
16
|
+
"""Base class for config files."""
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def get_path(self) -> Path:
|
|
20
|
+
"""Get the path to the config file."""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def load(self) -> dict[str, Any]:
|
|
24
|
+
"""Load the config file."""
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def dump(self, config: dict[str, Any]) -> None:
|
|
28
|
+
"""Dump the config file."""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get_configs(self) -> dict[str, Any]:
|
|
32
|
+
"""Get the config."""
|
|
33
|
+
|
|
34
|
+
def __init__(self) -> None:
|
|
35
|
+
"""Initialize the config file."""
|
|
36
|
+
self.path = self.get_path()
|
|
37
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
if not self.path.exists():
|
|
39
|
+
self.path.touch()
|
|
40
|
+
self.dump(self.get_configs())
|
|
41
|
+
|
|
42
|
+
if not self.is_correct():
|
|
43
|
+
config = self.add_missing_configs()
|
|
44
|
+
self.dump(config)
|
|
45
|
+
|
|
46
|
+
self.config = self.load()
|
|
47
|
+
if not self.is_correct():
|
|
48
|
+
msg = f"Config file {self.path} is not correct."
|
|
49
|
+
raise ValueError(msg)
|
|
50
|
+
|
|
51
|
+
def add_missing_configs(self) -> dict[str, Any]:
|
|
52
|
+
"""Add any missing configs to the config file."""
|
|
53
|
+
current_config = self.load()
|
|
54
|
+
expected_config = self.get_configs()
|
|
55
|
+
nested_structure_is_subset(
|
|
56
|
+
expected_config,
|
|
57
|
+
current_config,
|
|
58
|
+
self.add_missing_dict_val,
|
|
59
|
+
self.insert_missing_list_val,
|
|
60
|
+
)
|
|
61
|
+
return current_config
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def add_missing_dict_val(
|
|
65
|
+
expected_dict: dict[str, Any], actual_dict: dict[str, Any], key: str
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Add a missing dict value."""
|
|
68
|
+
actual_dict[key] = expected_dict[key]
|
|
69
|
+
|
|
70
|
+
@staticmethod
|
|
71
|
+
def insert_missing_list_val(
|
|
72
|
+
expected_list: list[Any], actual_list: list[Any], index: int
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Append a missing list value."""
|
|
75
|
+
actual_list.insert(index, expected_list[index])
|
|
76
|
+
|
|
77
|
+
def is_correct(self) -> bool:
|
|
78
|
+
"""Check if the config is correct.
|
|
79
|
+
|
|
80
|
+
If the file is empty, it is considered correct.
|
|
81
|
+
This is so bc if a user does not want a specific config file,
|
|
82
|
+
they can just make it empty and the tests will not fail.
|
|
83
|
+
"""
|
|
84
|
+
return self.is_unwanted() or self.is_correct_recursively(
|
|
85
|
+
self.get_configs(), self.load()
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def is_unwanted(self) -> bool:
|
|
89
|
+
"""Check if the config file is unwanted.
|
|
90
|
+
|
|
91
|
+
If the file is empty, it is considered unwanted.
|
|
92
|
+
"""
|
|
93
|
+
return self.path.exists() and self.path.read_text() == ""
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def is_correct_recursively(
|
|
97
|
+
expected_config: Any,
|
|
98
|
+
actual_config: Any,
|
|
99
|
+
) -> bool:
|
|
100
|
+
"""Check if the config is correct.
|
|
101
|
+
|
|
102
|
+
Checks if expected is a subset recursively of actual.
|
|
103
|
+
If a value is Any, it is considered correct.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
expected_config: The expected config
|
|
107
|
+
actual_config: The actual config
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
True if the config is correct, False otherwise
|
|
111
|
+
"""
|
|
112
|
+
return nested_structure_is_subset(expected_config, actual_config)
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def init_config_files(cls) -> None:
|
|
116
|
+
"""Initialize all subclasses."""
|
|
117
|
+
init_all_nonabstract_subclasses(cls, load_package_before=winipedia_utils)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class YamlConfigFile(ConfigFile):
|
|
121
|
+
"""Base class for yaml config files."""
|
|
122
|
+
|
|
123
|
+
def load(self) -> dict[str, Any]:
|
|
124
|
+
"""Load the config file."""
|
|
125
|
+
return yaml.safe_load(self.path.read_text()) or {}
|
|
126
|
+
|
|
127
|
+
def dump(self, config: dict[str, Any]) -> None:
|
|
128
|
+
"""Dump the config file."""
|
|
129
|
+
with self.path.open("w") as f:
|
|
130
|
+
yaml.safe_dump(config, f, sort_keys=False)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class TomlConfigFile(ConfigFile):
|
|
134
|
+
"""Base class for toml config files."""
|
|
135
|
+
|
|
136
|
+
def load(self) -> dict[str, Any]:
|
|
137
|
+
"""Load the config file."""
|
|
138
|
+
return tomlkit.parse(self.path.read_text())
|
|
139
|
+
|
|
140
|
+
def dump(self, config: dict[str, Any]) -> None:
|
|
141
|
+
"""Dump the config file."""
|
|
142
|
+
with self.path.open("w") as f:
|
|
143
|
+
tomlkit.dump(config, f)
|