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,48 +1,17 @@
1
1
  """Utilities for working with Python projects."""
2
2
 
3
- from types import ModuleType
3
+ from winipedia_utils.modules.module import create_module
4
+ from winipedia_utils.projects.poetry.config import (
5
+ PyprojectConfigFile, # avoid circular import
6
+ )
7
+ from winipedia_utils.text.config import (
8
+ ConfigFile, # avoid circular import
9
+ )
4
10
 
5
- from winipedia_utils.modules.module import create_module, to_path
6
- from winipedia_utils.modules.package import get_src_package
7
- from winipedia_utils.projects.poetry.config import get_poetry_package_name
8
11
 
9
-
10
- def _create_project_root() -> None:
12
+ def create_project_root() -> None:
11
13
  """Create the project root."""
12
- src_package_name = get_poetry_package_name()
13
- create_module(src_package_name, is_package=True)
14
- _create_py_typed()
15
-
16
-
17
- def _create_py_typed() -> None:
18
- """Create the py.typed file."""
19
- src_package_name = get_src_package().__name__
20
- py_typed_path = to_path(src_package_name, is_package=True) / "py.typed"
21
- py_typed_path.touch()
22
-
14
+ ConfigFile.init_config_files()
23
15
 
24
- def make_name_from_package(
25
- package: ModuleType,
26
- split_on: str = "_",
27
- join_on: str = "-",
28
- *,
29
- capitalize: bool = True,
30
- ) -> str:
31
- """Make a name from a package.
32
-
33
- takes a package and makes a name from it that is readable by humans.
34
-
35
- Args:
36
- package (ModuleType): The package to make a name from
37
- split_on (str, optional): what to split the package name on. Defaults to "_".
38
- join_on (str, optional): what to join the package name with. Defaults to "-".
39
- capitalize (bool, optional): Whether to capitalize each part. Defaults to True.
40
-
41
- Returns:
42
- str: _description_
43
- """
44
- package_name = package.__name__.split(".")[-1]
45
- parts = package_name.split(split_on)
46
- if capitalize:
47
- parts = [part.capitalize() for part in parts]
48
- return join_on.join(parts)
16
+ src_package_name = PyprojectConfigFile.get_package_name()
17
+ create_module(src_package_name, is_package=True)
winipedia_utils/setup.py CHANGED
@@ -9,51 +9,33 @@ This script is intended to be called once at the beginning of a project.
9
9
  from collections.abc import Callable
10
10
  from typing import Any
11
11
 
12
- from winipedia_utils.git.gitignore.gitignore import _add_package_patterns_to_gitignore
13
- from winipedia_utils.git.pre_commit.config import (
14
- _add_package_hook_to_pre_commit_config,
15
- _pre_commit_install,
16
- )
17
- from winipedia_utils.git.pre_commit.run_hooks import _run_all_hooks
18
- from winipedia_utils.git.workflows.publish import _add_publish_workflow
19
- from winipedia_utils.git.workflows.release import _add_release_workflow
12
+ from winipedia_utils.git.gitignore.config import GitIgnoreConfigFile
13
+ from winipedia_utils.git.pre_commit.run_hooks import run_hooks
20
14
  from winipedia_utils.logging.logger import get_logger
21
- from winipedia_utils.projects.poetry.config import (
22
- _add_configurations_to_pyproject_toml,
23
- )
24
- from winipedia_utils.projects.poetry.poetry import (
25
- _install_dev_dependencies,
26
- )
27
- from winipedia_utils.projects.project import _create_project_root
15
+ from winipedia_utils.projects.project import create_project_root
28
16
 
29
17
  logger = get_logger(__name__)
30
18
 
31
19
 
32
- SETUP_STEPS = [
33
- _install_dev_dependencies,
34
- _add_package_hook_to_pre_commit_config,
35
- _pre_commit_install,
36
- _add_package_patterns_to_gitignore,
37
- _add_release_workflow,
38
- _add_publish_workflow,
39
- _add_configurations_to_pyproject_toml,
40
- _create_project_root,
41
- _run_all_hooks,
20
+ SETUP_STEPS: list[Callable[..., Any]] = [
21
+ GitIgnoreConfigFile, # must be first
22
+ create_project_root,
23
+ run_hooks,
42
24
  ]
43
25
 
44
26
 
45
- def _get_setup_steps() -> list[Callable[..., Any]]:
27
+ def get_setup_steps() -> list[Callable[..., Any]]:
46
28
  """Get the setup steps."""
47
29
  return SETUP_STEPS
48
30
 
49
31
 
50
- def _setup() -> None:
32
+ def setup() -> None:
51
33
  """Set up the project."""
52
- for step in _get_setup_steps():
34
+ for step in get_setup_steps():
53
35
  logger.info("Running setup step: %s", step.__name__)
54
36
  step()
55
37
  logger.info("Setup complete!")
56
38
 
57
39
 
58
40
  if __name__ == "__main__":
59
- _setup()
41
+ setup()
@@ -0,0 +1,127 @@
1
+ """Config utilities for testing."""
2
+
3
+ from abc import abstractmethod
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
8
+ from winipedia_utils.text.config import ConfigFile
9
+
10
+
11
+ class PythonConfigFile(ConfigFile):
12
+ """Base class for python config files."""
13
+
14
+ CONTENT_KEY = "content"
15
+
16
+ @classmethod
17
+ def load(cls) -> dict[str, str]:
18
+ """Load the config file."""
19
+ return {cls.CONTENT_KEY: cls.get_path().read_text()}
20
+
21
+ @classmethod
22
+ def dump(cls, config: dict[str, Any] | list[Any]) -> None:
23
+ """Dump the config file."""
24
+ if not isinstance(config, dict):
25
+ msg = f"Cannot dump {config} to python file."
26
+ raise TypeError(msg)
27
+ cls.get_path().write_text(config[cls.CONTENT_KEY])
28
+
29
+ @classmethod
30
+ def get_file_extension(cls) -> str:
31
+ """Get the file extension of the config file."""
32
+ return "py"
33
+
34
+ @classmethod
35
+ def get_configs(cls) -> dict[str, Any]:
36
+ """Get the config."""
37
+ return {cls.CONTENT_KEY: cls.get_content_str()}
38
+
39
+ @classmethod
40
+ def get_file_content(cls) -> str:
41
+ """Get the file content."""
42
+ return cls.load()[cls.CONTENT_KEY]
43
+
44
+ @classmethod
45
+ @abstractmethod
46
+ def get_content_str(cls) -> str:
47
+ """Get the content."""
48
+
49
+ @classmethod
50
+ def is_correct(cls) -> bool:
51
+ """Check if the config is correct.
52
+
53
+ Python files are correct if they exist and contain the correct content.
54
+ """
55
+ return (
56
+ super().is_correct()
57
+ or cls.get_content_str().strip() in cls.load()[cls.CONTENT_KEY]
58
+ )
59
+
60
+
61
+ class PythonTestsConfigFile(PythonConfigFile):
62
+ """Base class for python config files in the tests directory."""
63
+
64
+ @classmethod
65
+ def get_parent_path(cls) -> Path:
66
+ """Get the path to the config file."""
67
+ return Path(TESTS_PACKAGE_NAME)
68
+
69
+
70
+ class ConftestConfigFile(PythonTestsConfigFile):
71
+ """Config file for conftest.py."""
72
+
73
+ @classmethod
74
+ def get_content_str(cls) -> str:
75
+ """Get the config content."""
76
+ return '''"""Pytest configuration for tests.
77
+
78
+ This module configures pytest plugins for the test suite, setting up the necessary
79
+ fixtures and hooks for the different
80
+ test scopes (function, class, module, package, session).
81
+ It also import custom plugins from tests/base/scopes.
82
+ This file should not be modified manually.
83
+ """
84
+
85
+ pytest_plugins = ["winipedia_utils.testing.tests.conftest"]
86
+ '''
87
+
88
+
89
+ class ZeroTestConfigFile(PythonTestsConfigFile):
90
+ """Config file for test_zero.py."""
91
+
92
+ @classmethod
93
+ def get_filename(cls) -> str:
94
+ """Get the filename of the config file."""
95
+ filename = super().get_filename()
96
+ return "_".join(reversed(filename.split("_")))
97
+
98
+ @classmethod
99
+ def get_content_str(cls) -> str:
100
+ """Get the config."""
101
+ return '''"""Contains an empty test."""
102
+
103
+
104
+ def test_zero() -> None:
105
+ """Empty test.
106
+
107
+ Exists so that when no tests are written yet the base fixtures are executed.
108
+ """
109
+ '''
110
+
111
+
112
+ class ExperimentConfigFile(PythonConfigFile):
113
+ """Config file for experiment.py.
114
+
115
+ Is at root level and in .gitignore for experimentation.
116
+ """
117
+
118
+ @classmethod
119
+ def get_parent_path(cls) -> Path:
120
+ """Get the path to the config file."""
121
+ return Path()
122
+
123
+ @classmethod
124
+ def get_content_str(cls) -> str:
125
+ """Get the config."""
126
+ return '''"""This file is for experimentation and is ignored by git."""
127
+ '''
@@ -6,7 +6,6 @@ It creates the basic test structure and generates skeleton test functions with
6
6
  NotImplementedError to indicate tests that need to be written.
7
7
  """
8
8
 
9
- from pathlib import Path
10
9
  from types import ModuleType
11
10
  from typing import cast
12
11
 
@@ -15,11 +14,11 @@ from winipedia_utils.modules.class_ import (
15
14
  get_all_methods_from_cls,
16
15
  )
17
16
  from winipedia_utils.modules.function import get_all_functions_from_module
17
+ from winipedia_utils.modules.inspection import get_qualname_of_obj
18
18
  from winipedia_utils.modules.module import (
19
19
  create_module,
20
20
  get_isolated_obj_name,
21
21
  get_module_content_as_str,
22
- get_qualname_of_obj,
23
22
  to_path,
24
23
  )
25
24
  from winipedia_utils.modules.package import (
@@ -28,19 +27,13 @@ from winipedia_utils.modules.package import (
28
27
  walk_package,
29
28
  )
30
29
  from winipedia_utils.testing import tests
30
+ from winipedia_utils.testing.config import ConftestConfigFile, ZeroTestConfigFile
31
31
  from winipedia_utils.testing.convention import (
32
- TESTS_PACKAGE_NAME,
33
32
  get_test_obj_from_obj,
34
33
  make_test_obj_importpath_from_obj,
35
34
  make_test_obj_name,
36
35
  reverse_make_test_obj_name,
37
36
  )
38
- from winipedia_utils.testing.tests.base.utils.utils import (
39
- _conftest_content_is_correct,
40
- _get_conftest_content,
41
- _get_test_0_content,
42
- _test_0_content_is_correct,
43
- )
44
37
 
45
38
 
46
39
  def create_tests() -> None:
@@ -63,21 +56,14 @@ def create_tests_base() -> None:
63
56
  4. Creates a conftest.py file with the appropriate pytest plugin configuration
64
57
  5. Does not overwrite anything if it already exists except conftest.py
65
58
  """
66
- tests_path = Path(TESTS_PACKAGE_NAME)
67
59
  copy_package(
68
60
  src_package=tests,
69
61
  dst=".",
70
62
  with_file_content=False,
71
63
  )
72
- # write pytest_plugin in the conftest.py
73
- conftest_path = tests_path / "conftest.py"
74
- # if conftest does not exist or the content is not the same, overwrite it
75
- if not _conftest_content_is_correct(conftest_path):
76
- conftest_path.write_text(_get_conftest_content())
77
-
78
- test_0_path = tests_path / "test_0.py"
79
- if not _test_0_content_is_correct(test_0_path):
80
- test_0_path.write_text(_get_test_0_content())
64
+ # write the config files
65
+ _ = ConftestConfigFile()
66
+ _ = ZeroTestConfigFile()
81
67
 
82
68
 
83
69
  def create_tests_for_src_package() -> None:
@@ -0,0 +1,19 @@
1
+ """Has utils towards skipping tests."""
2
+
3
+ import functools
4
+
5
+ import pytest
6
+
7
+ from winipedia_utils.git.github.github import running_in_github_actions
8
+
9
+ skip_fixture_test: pytest.MarkDecorator = functools.partial(
10
+ pytest.mark.skip,
11
+ reason="Fixtures are not testable bc they cannot be called directly.",
12
+ )()
13
+
14
+
15
+ skip_in_github_actions: pytest.MarkDecorator = functools.partial(
16
+ pytest.mark.skipif,
17
+ running_in_github_actions(),
18
+ reason="Test cannot run in GitHub action.",
19
+ )()
@@ -4,3 +4,39 @@ This module provides custom fixtures for pytest that can be used to
4
4
  automate common testing tasks and provide consistent setup and teardown
5
5
  for tests.
6
6
  """
7
+
8
+ from collections.abc import Callable
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ from winipedia_utils.text.config import ConfigFile
14
+
15
+
16
+ @pytest.fixture
17
+ def config_file_factory[T: ConfigFile](
18
+ tmp_path: Path,
19
+ ) -> Callable[[type[T]], type[T]]:
20
+ """Factory fixture for creating config file classes with tmp_path.
21
+
22
+ This factory wraps any ConfigFile subclass to use tmp_path for get_path().
23
+ Define tmp_path once here, then all test config classes inherit it.
24
+ """
25
+
26
+ def _make_test_config(
27
+ base_class: type[T],
28
+ ) -> type[T]:
29
+ """Create a test config class that uses tmp_path."""
30
+
31
+ class TestConfigFile(base_class): # type: ignore [misc, valid-type]
32
+ """Test config file with tmp_path override."""
33
+
34
+ @classmethod
35
+ def get_path(cls) -> Path:
36
+ """Get the path to the config file in tmp_path."""
37
+ path = super().get_path()
38
+ return Path(tmp_path / path)
39
+
40
+ return TestConfigFile
41
+
42
+ return _make_test_config
@@ -9,11 +9,11 @@ mechanism.
9
9
  import pytest
10
10
 
11
11
  from winipedia_utils.testing.fixtures import autouse_class_fixture
12
- from winipedia_utils.testing.tests.base.utils.utils import _assert_no_untested_objs
12
+ from winipedia_utils.testing.tests.base.utils.utils import assert_no_untested_objs
13
13
 
14
14
 
15
15
  @autouse_class_fixture
16
- def _test_all_methods_tested(request: pytest.FixtureRequest) -> None:
16
+ def assert_all_methods_tested(request: pytest.FixtureRequest) -> None:
17
17
  """Verify that all methods in a class have corresponding tests.
18
18
 
19
19
  This fixture runs automatically for each test class and checks that every
@@ -30,4 +30,4 @@ def _test_all_methods_tested(request: pytest.FixtureRequest) -> None:
30
30
  class_ = request.node.cls
31
31
  if class_ is None:
32
32
  return
33
- _assert_no_untested_objs(class_)
33
+ assert_no_untested_objs(class_)
@@ -6,14 +6,19 @@ These fixtures are automatically applied to all test modules through pytest's au
6
6
  mechanism.
7
7
  """
8
8
 
9
+ from typing import TYPE_CHECKING
10
+
9
11
  import pytest
10
12
 
11
13
  from winipedia_utils.testing.fixtures import autouse_module_fixture
12
- from winipedia_utils.testing.tests.base.utils.utils import _assert_no_untested_objs
14
+ from winipedia_utils.testing.tests.base.utils.utils import assert_no_untested_objs
15
+
16
+ if TYPE_CHECKING:
17
+ from types import ModuleType
13
18
 
14
19
 
15
20
  @autouse_module_fixture
16
- def _test_all_funcs_and_classes_tested(request: pytest.FixtureRequest) -> None:
21
+ def assert_all_funcs_and_classes_tested(request: pytest.FixtureRequest) -> None:
17
22
  """Verify that all functions and classes in a module have corresponding tests.
18
23
 
19
24
  This fixture runs automatically for each test module and checks that every
@@ -27,7 +32,5 @@ def _test_all_funcs_and_classes_tested(request: pytest.FixtureRequest) -> None:
27
32
  AssertionError: If any function or class in the source module lacks a test
28
33
 
29
34
  """
30
- module = request.module
31
- if module.__name__ == "tests.test_0":
32
- return
33
- _assert_no_untested_objs(module)
35
+ module: ModuleType = request.module
36
+ assert_no_untested_objs(module)