winipedia-utils 0.3.43__py3-none-any.whl → 0.4.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.

Potentially problematic release.


This version of winipedia-utils might be problematic. Click here for more details.

Files changed (33) hide show
  1. winipedia_utils/git/github/repo/__init__.py +1 -0
  2. winipedia_utils/git/github/repo/protect.py +104 -0
  3. winipedia_utils/git/github/repo/repo.py +205 -0
  4. winipedia_utils/git/github/workflows/base/__init__.py +1 -0
  5. winipedia_utils/git/{workflows → github/workflows}/base/base.py +95 -51
  6. winipedia_utils/git/github/workflows/health_check.py +55 -0
  7. winipedia_utils/git/{workflows → github/workflows}/publish.py +11 -8
  8. winipedia_utils/git/github/workflows/release.py +45 -0
  9. winipedia_utils/git/gitignore/config.py +49 -29
  10. winipedia_utils/git/gitignore/gitignore.py +1 -1
  11. winipedia_utils/git/pre_commit/config.py +18 -13
  12. winipedia_utils/git/pre_commit/hooks.py +22 -4
  13. winipedia_utils/git/pre_commit/run_hooks.py +2 -1
  14. winipedia_utils/iterating/iterate.py +3 -4
  15. winipedia_utils/modules/module.py +2 -0
  16. winipedia_utils/modules/package.py +2 -1
  17. winipedia_utils/projects/poetry/config.py +74 -36
  18. winipedia_utils/projects/project.py +2 -2
  19. winipedia_utils/setup.py +2 -0
  20. winipedia_utils/testing/config.py +83 -29
  21. winipedia_utils/testing/tests/base/fixtures/fixture.py +36 -0
  22. winipedia_utils/testing/tests/base/fixtures/scopes/module.py +6 -5
  23. winipedia_utils/testing/tests/base/fixtures/scopes/session.py +7 -8
  24. winipedia_utils/testing/tests/base/utils/utils.py +43 -2
  25. winipedia_utils/text/config.py +84 -37
  26. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.12.dist-info}/METADATA +23 -8
  27. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.12.dist-info}/RECORD +31 -27
  28. winipedia_utils/git/workflows/health_check.py +0 -51
  29. winipedia_utils/git/workflows/release.py +0 -33
  30. /winipedia_utils/git/{workflows/base → github}/__init__.py +0 -0
  31. /winipedia_utils/git/{workflows → github/workflows}/__init__.py +0 -0
  32. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.12.dist-info}/WHEEL +0 -0
  33. {winipedia_utils-0.3.43.dist-info → winipedia_utils-0.4.12.dist-info}/licenses/LICENSE +0 -0
@@ -13,34 +13,65 @@ class PythonConfigFile(ConfigFile):
13
13
 
14
14
  CONTENT_KEY = "content"
15
15
 
16
- def load(self) -> dict[str, str]:
16
+ @classmethod
17
+ def load(cls) -> dict[str, str]:
17
18
  """Load the config file."""
18
- return {self.CONTENT_KEY: self.path.read_text()}
19
+ return {cls.CONTENT_KEY: cls.get_path().read_text()}
19
20
 
20
- def dump(self, config: dict[str, Any]) -> None:
21
+ @classmethod
22
+ def dump(cls, config: dict[str, Any] | list[Any]) -> None:
21
23
  """Dump the config file."""
22
- self.path.write_text(config[self.CONTENT_KEY])
23
-
24
- def get_configs(self) -> dict[str, Any]:
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]:
25
36
  """Get the config."""
26
- return {self.CONTENT_KEY: self.get_content()}
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]
27
43
 
44
+ @classmethod
28
45
  @abstractmethod
29
- def get_content(self) -> str:
46
+ def get_content_str(cls) -> str:
30
47
  """Get the content."""
31
- return self.load()[self.CONTENT_KEY]
32
48
 
49
+ @classmethod
50
+ def is_correct(cls) -> bool:
51
+ """Check if the config is correct.
33
52
 
34
- class ConftestConfigFile(PythonConfigFile):
35
- """Config file for conftest.py."""
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
+ )
36
59
 
37
- PATH = Path(f"{TESTS_PACKAGE_NAME}/conftest.py")
38
60
 
39
- def get_path(self) -> Path:
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:
40
66
  """Get the path to the config file."""
41
- return self.PATH
67
+ return Path(TESTS_PACKAGE_NAME)
68
+
42
69
 
43
- def get_content(self) -> str:
70
+ class ConftestConfigFile(PythonTestsConfigFile):
71
+ """Config file for conftest.py."""
72
+
73
+ @classmethod
74
+ def get_content_str(cls) -> str:
44
75
  """Get the config content."""
45
76
  return '''"""Pytest configuration for tests.
46
77
 
@@ -55,21 +86,22 @@ pytest_plugins = ["winipedia_utils.testing.tests.conftest"]
55
86
  '''
56
87
 
57
88
 
58
- class ZeroTestConfigFile(PythonConfigFile):
59
- """Config file for test_0.py."""
60
-
61
- PATH = Path(f"{TESTS_PACKAGE_NAME}/test_0.py")
89
+ class ZeroTestConfigFile(PythonTestsConfigFile):
90
+ """Config file for test_zero.py."""
62
91
 
63
- def get_path(self) -> Path:
64
- """Get the path to the config file."""
65
- return self.PATH
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("_")))
66
97
 
67
- def get_content(self) -> str:
98
+ @classmethod
99
+ def get_content_str(cls) -> str:
68
100
  """Get the config."""
69
101
  return '''"""Contains an empty test."""
70
102
 
71
103
 
72
- def test_0() -> None:
104
+ def test_zero() -> None:
73
105
  """Empty test.
74
106
 
75
107
  Exists so that when no tests are written yet the base fixtures are executed.
@@ -83,13 +115,35 @@ class ExperimentConfigFile(PythonConfigFile):
83
115
  Is at root level and in .gitignore for experimentation.
84
116
  """
85
117
 
86
- PATH = Path("experiment.py")
87
-
88
- def get_path(self) -> Path:
118
+ @classmethod
119
+ def get_parent_path(cls) -> Path:
89
120
  """Get the path to the config file."""
90
- return self.PATH
121
+ return Path()
91
122
 
92
- def get_content(self) -> str:
123
+ @classmethod
124
+ def get_content_str(cls) -> str:
93
125
  """Get the config."""
94
126
  return '''"""This file is for experimentation and is ignored by git."""
95
127
  '''
128
+
129
+
130
+ class LocalSecretsConfigFile(PythonConfigFile):
131
+ """Config file for secrets.py.
132
+
133
+ Config file for secrets. Is added to .gitignore automatically.
134
+ Should be in .gitignore.
135
+ """
136
+
137
+ @classmethod
138
+ def get_parent_path(cls) -> Path:
139
+ """Get the path to the config file."""
140
+ return Path()
141
+
142
+ @classmethod
143
+ def get_content_str(cls) -> str:
144
+ """Get the config."""
145
+ return '''"""This file is for secrets you might use and is ignored by git.
146
+
147
+ Can be used by tests or other developing code.
148
+ """
149
+ '''
@@ -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
@@ -6,13 +6,16 @@ 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
- from winipedia_utils.modules.module import to_module_name
12
- from winipedia_utils.testing.config import ZeroTestConfigFile
13
13
  from winipedia_utils.testing.fixtures import autouse_module_fixture
14
14
  from winipedia_utils.testing.tests.base.utils.utils import assert_no_untested_objs
15
15
 
16
+ if TYPE_CHECKING:
17
+ from types import ModuleType
18
+
16
19
 
17
20
  @autouse_module_fixture
18
21
  def assert_all_funcs_and_classes_tested(request: pytest.FixtureRequest) -> None:
@@ -29,7 +32,5 @@ def assert_all_funcs_and_classes_tested(request: pytest.FixtureRequest) -> None:
29
32
  AssertionError: If any function or class in the source module lacks a test
30
33
 
31
34
  """
32
- module = request.module
33
- if module.__name__ == to_module_name(ZeroTestConfigFile().get_path()):
34
- return
35
+ module: ModuleType = request.module
35
36
  assert_no_untested_objs(module)
@@ -16,7 +16,7 @@ from winipedia_utils.modules.package import (
16
16
  walk_package,
17
17
  )
18
18
  from winipedia_utils.projects.poetry.config import (
19
- PyProjectTomlConfig,
19
+ PyprojectConfigFile,
20
20
  )
21
21
  from winipedia_utils.testing.assertions import assert_with_msg
22
22
  from winipedia_utils.testing.convention import (
@@ -39,15 +39,14 @@ def assert_dev_dependencies_config_is_correct() -> None:
39
39
  AssertionError: If the dev dependencies in consts.py are not correct
40
40
 
41
41
  """
42
- config = PyProjectTomlConfig()
43
- if config.get_package_name() != winipedia_utils.__name__:
42
+ if PyprojectConfigFile.get_package_name() != winipedia_utils.__name__:
44
43
  # this const is only used in winipedia_utils
45
44
  # to be able to install them with setup.py
46
45
  return
47
- actual_dev_dependencies = config.get_dev_dependencies()
48
- expected_dev_dependencies = config.get_configs()["tool"]["poetry"]["group"]["dev"][
49
- "dependencies"
50
- ].keys()
46
+ actual_dev_dependencies = PyprojectConfigFile.get_dev_dependencies()
47
+ expected_dev_dependencies = PyprojectConfigFile.get_configs()["tool"]["poetry"][
48
+ "group"
49
+ ]["dev"]["dependencies"].keys()
51
50
  assert_with_msg(
52
51
  set(actual_dev_dependencies) == set(expected_dev_dependencies),
53
52
  "Dev dependencies in consts.py are not correct",
@@ -123,7 +122,7 @@ def assert_src_package_correctly_named() -> None:
123
122
 
124
123
  """
125
124
  src_package = get_src_package().__name__
126
- config = PyProjectTomlConfig()
125
+ config = PyprojectConfigFile()
127
126
  expected_package = config.get_package_name()
128
127
  assert_with_msg(
129
128
  src_package == expected_package,
@@ -9,19 +9,29 @@ Returns:
9
9
 
10
10
  """
11
11
 
12
+ import os
12
13
  from collections.abc import Callable
13
14
  from types import ModuleType
14
15
  from typing import Any
15
16
 
17
+ from winipedia_utils.logging.logger import get_logger
16
18
  from winipedia_utils.modules.function import is_abstractmethod
17
- from winipedia_utils.modules.module import get_objs_from_obj, make_obj_importpath
19
+ from winipedia_utils.modules.module import (
20
+ get_objs_from_obj,
21
+ import_obj_from_importpath,
22
+ make_obj_importpath,
23
+ to_module_name,
24
+ )
18
25
  from winipedia_utils.testing.assertions import assert_with_msg
26
+ from winipedia_utils.testing.config import LocalSecretsConfigFile
19
27
  from winipedia_utils.testing.convention import (
20
28
  get_obj_from_test_obj,
21
29
  make_test_obj_importpath_from_obj,
22
30
  make_untested_summary_error_msg,
23
31
  )
24
32
 
33
+ logger = get_logger(__name__)
34
+
25
35
 
26
36
  def assert_no_untested_objs(
27
37
  test_obj: ModuleType | type | Callable[..., Any],
@@ -42,7 +52,15 @@ def assert_no_untested_objs(
42
52
  test_objs = get_objs_from_obj(test_obj)
43
53
  test_objs_paths = {make_obj_importpath(o) for o in test_objs}
44
54
 
45
- obj = get_obj_from_test_obj(test_obj)
55
+ try:
56
+ obj = get_obj_from_test_obj(test_obj)
57
+ except ImportError:
58
+ if isinstance(test_obj, ModuleType):
59
+ # we skip if module not found bc that means it has custom tests
60
+ # and is not part of the mirrored structure
61
+ logger.warning("No source module found for %s, skipping", test_obj)
62
+ return
63
+ raise
46
64
  objs = get_objs_from_obj(obj)
47
65
  supposed_test_objs_paths = {make_test_obj_importpath_from_obj(o) for o in objs}
48
66
 
@@ -65,3 +83,26 @@ def assert_isabstrct_method(method: Any) -> None:
65
83
  is_abstractmethod(method),
66
84
  f"Expected {method} to be abstract method",
67
85
  )
86
+
87
+
88
+ def get_github_repo_token() -> str:
89
+ """Get the GitHub token."""
90
+ # try os env first
91
+ token = os.getenv("REPO_TOKEN")
92
+ if token:
93
+ return token
94
+
95
+ local_secrets_module_path = to_module_name(LocalSecretsConfigFile.get_path())
96
+ local_secrets_module = import_obj_from_importpath(local_secrets_module_path)
97
+ token = getattr(local_secrets_module, "REPO_TOKEN", None)
98
+ if not isinstance(token, str):
99
+ msg = f"Expected REPO_TOKEN to be str, got {type(token)}"
100
+ raise TypeError(msg)
101
+ if token:
102
+ return token
103
+
104
+ msg = (
105
+ f"No token named REPO_TOKEN found "
106
+ f"in github secrets or {LocalSecretsConfigFile.get_path()}"
107
+ )
108
+ raise ValueError(msg)
@@ -1,5 +1,6 @@
1
1
  """Base class for config files."""
2
2
 
3
+ import inspect
3
4
  from abc import ABC, abstractmethod
4
5
  from pathlib import Path
5
6
  from typing import Any
@@ -13,53 +14,80 @@ from winipedia_utils.modules.class_ import init_all_nonabstract_subclasses
13
14
  from winipedia_utils.projects.poetry.poetry import (
14
15
  get_python_module_script,
15
16
  )
17
+ from winipedia_utils.text.string import split_on_uppercase
16
18
 
17
19
 
18
20
  class ConfigFile(ABC):
19
21
  """Base class for config files."""
20
22
 
23
+ @classmethod
21
24
  @abstractmethod
22
- def get_path(self) -> Path:
25
+ def get_parent_path(cls) -> Path:
23
26
  """Get the path to the config file."""
24
27
 
28
+ @classmethod
25
29
  @abstractmethod
26
- def load(self) -> dict[str, Any]:
30
+ def load(cls) -> dict[str, Any] | list[Any]:
27
31
  """Load the config file."""
28
32
 
33
+ @classmethod
29
34
  @abstractmethod
30
- def dump(self, config: dict[str, Any]) -> None:
35
+ def dump(cls, config: dict[str, Any] | list[Any]) -> None:
31
36
  """Dump the config file."""
32
37
 
38
+ @classmethod
33
39
  @abstractmethod
34
- def get_configs(self) -> dict[str, Any]:
40
+ def get_file_extension(cls) -> str:
41
+ """Get the file extension of the config file."""
42
+
43
+ @classmethod
44
+ @abstractmethod
45
+ def get_configs(cls) -> dict[str, Any] | list[Any]:
35
46
  """Get the config."""
36
47
 
37
48
  def __init__(self) -> None:
38
49
  """Initialize the config file."""
39
- self.path = self.get_path()
40
- self.path.parent.mkdir(parents=True, exist_ok=True)
41
- if not self.path.exists():
42
- self.path.touch()
50
+ self.get_path().parent.mkdir(parents=True, exist_ok=True)
51
+ if not self.get_path().exists():
52
+ self.get_path().touch()
43
53
  self.dump(self.get_configs())
44
54
 
45
55
  if not self.is_correct():
46
56
  config = self.add_missing_configs()
47
57
  self.dump(config)
48
58
 
49
- self.config = self.load()
50
59
  if not self.is_correct():
51
- msg = f"Config file {self.path} is not correct."
60
+ msg = f"Config file {self.get_path()} is not correct."
52
61
  raise ValueError(msg)
53
62
 
54
- def add_missing_configs(self) -> dict[str, Any]:
63
+ @classmethod
64
+ def get_path(cls) -> Path:
65
+ """Get the path to the config file."""
66
+ return (
67
+ cls.get_parent_path() / f"{cls.get_filename()}.{cls.get_file_extension()}"
68
+ )
69
+
70
+ @classmethod
71
+ def get_filename(cls) -> str:
72
+ """Get the filename of the config file."""
73
+ name = cls.__name__
74
+ abstract_parents = [
75
+ parent.__name__ for parent in cls.__mro__ if inspect.isabstract(parent)
76
+ ]
77
+ for parent in abstract_parents:
78
+ name = name.removesuffix(parent)
79
+ return "_".join(split_on_uppercase(name)).lower()
80
+
81
+ @classmethod
82
+ def add_missing_configs(cls) -> dict[str, Any] | list[Any]:
55
83
  """Add any missing configs to the config file."""
56
- current_config = self.load()
57
- expected_config = self.get_configs()
84
+ current_config = cls.load()
85
+ expected_config = cls.get_configs()
58
86
  nested_structure_is_subset(
59
87
  expected_config,
60
88
  current_config,
61
- self.add_missing_dict_val,
62
- self.insert_missing_list_val,
89
+ cls.add_missing_dict_val,
90
+ cls.insert_missing_list_val,
63
91
  )
64
92
  return current_config
65
93
 
@@ -77,28 +105,30 @@ class ConfigFile(ABC):
77
105
  """Append a missing list value."""
78
106
  actual_list.insert(index, expected_list[index])
79
107
 
80
- def is_correct(self) -> bool:
108
+ @classmethod
109
+ def is_correct(cls) -> bool:
81
110
  """Check if the config is correct.
82
111
 
83
112
  If the file is empty, it is considered correct.
84
113
  This is so bc if a user does not want a specific config file,
85
114
  they can just make it empty and the tests will not fail.
86
115
  """
87
- return self.is_unwanted() or self.is_correct_recursively(
88
- self.get_configs(), self.load()
116
+ return cls.is_unwanted() or cls.is_correct_recursively(
117
+ cls.get_configs(), cls.load()
89
118
  )
90
119
 
91
- def is_unwanted(self) -> bool:
120
+ @classmethod
121
+ def is_unwanted(cls) -> bool:
92
122
  """Check if the config file is unwanted.
93
123
 
94
124
  If the file is empty, it is considered unwanted.
95
125
  """
96
- return self.path.exists() and self.path.read_text() == ""
126
+ return cls.get_path().exists() and cls.get_path().read_text() == ""
97
127
 
98
128
  @staticmethod
99
129
  def is_correct_recursively(
100
- expected_config: Any,
101
- actual_config: Any,
130
+ expected_config: dict[str, Any] | list[Any],
131
+ actual_config: dict[str, Any] | list[Any],
102
132
  ) -> bool:
103
133
  """Check if the config is correct.
104
134
 
@@ -119,35 +149,52 @@ class ConfigFile(ABC):
119
149
  """Initialize all subclasses."""
120
150
  init_all_nonabstract_subclasses(cls, load_package_before=winipedia_utils)
121
151
 
152
+ @staticmethod
153
+ def get_python_setup_script() -> str:
154
+ """Get the poetry run setup script."""
155
+ from winipedia_utils import setup # noqa: PLC0415 # avoid circular import
156
+
157
+ return get_python_module_script(setup)
158
+
122
159
 
123
160
  class YamlConfigFile(ConfigFile):
124
161
  """Base class for yaml config files."""
125
162
 
126
- def load(self) -> dict[str, Any]:
163
+ @classmethod
164
+ def load(cls) -> dict[str, Any] | list[Any]:
127
165
  """Load the config file."""
128
- return yaml.safe_load(self.path.read_text()) or {}
166
+ return yaml.safe_load(cls.get_path().read_text()) or {}
129
167
 
130
- def dump(self, config: dict[str, Any]) -> None:
168
+ @classmethod
169
+ def dump(cls, config: dict[str, Any] | list[Any]) -> None:
131
170
  """Dump the config file."""
132
- with self.path.open("w") as f:
171
+ with cls.get_path().open("w") as f:
133
172
  yaml.safe_dump(config, f, sort_keys=False)
134
173
 
135
- @staticmethod
136
- def get_python_setup_script() -> str:
137
- """Get the poetry run setup script."""
138
- from winipedia_utils import setup # noqa: PLC0415 # avoid circular import
139
-
140
- return get_python_module_script(setup)
174
+ @classmethod
175
+ def get_file_extension(cls) -> str:
176
+ """Get the file extension of the config file."""
177
+ return "yaml"
141
178
 
142
179
 
143
180
  class TomlConfigFile(ConfigFile):
144
181
  """Base class for toml config files."""
145
182
 
146
- def load(self) -> dict[str, Any]:
183
+ @classmethod
184
+ def load(cls) -> dict[str, Any]:
147
185
  """Load the config file."""
148
- return tomlkit.parse(self.path.read_text())
186
+ return tomlkit.parse(cls.get_path().read_text())
149
187
 
150
- def dump(self, config: dict[str, Any]) -> None:
188
+ @classmethod
189
+ def dump(cls, config: dict[str, Any] | list[Any]) -> None:
151
190
  """Dump the config file."""
152
- with self.path.open("w") as f:
153
- tomlkit.dump(config, f)
191
+ if not isinstance(config, dict):
192
+ msg = f"Cannot dump {config} to toml file."
193
+ raise TypeError(msg)
194
+ with cls.get_path().open("w") as f:
195
+ tomlkit.dump(config, f, sort_keys=False)
196
+
197
+ @classmethod
198
+ def get_file_extension(cls) -> str:
199
+ """Get the file extension of the config file."""
200
+ return "toml"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winipedia-utils
3
- Version: 0.3.43
3
+ Version: 0.4.12
4
4
  Summary: A package with many utility functions
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -16,6 +16,7 @@ Requires-Dist: defusedxml
16
16
  Requires-Dist: keyring
17
17
  Requires-Dist: pathspec
18
18
  Requires-Dist: polars
19
+ Requires-Dist: pygithub
19
20
  Requires-Dist: pyyaml
20
21
  Requires-Dist: setuptools
21
22
  Requires-Dist: tomlkit
@@ -62,13 +63,26 @@ A comprehensive Python utility package that enforces best practices, automates p
62
63
 
63
64
  ## Quick Start
64
65
 
65
- ### Installation
66
+ ### How to setup a new project
66
67
 
67
68
  ```bash
68
- # Add winipedia-utils to your project
69
+ # 1: Create a new repository on GitHub
70
+ # The default branch must be called main
71
+ # add a PAT or Fine-Grained Access Token to your repo secrets called REPO_TOKEN that has write access to the repository (needed for branch protection in health_check.yaml - see winipedia_utils.git.github.repo.protect)
72
+
73
+ # 2: Clone the repository
74
+ git clone https://github.com/owner/repo.git
75
+
76
+ # 3: Create a new poetry project
77
+ poetry init # or poetry new
78
+ # 4: Poetry will ask you some stuff when you run poetry init.
79
+ # First author name must be equal to the GitHub repository owner (username).
80
+ # The repository name must be equal to the package/project name.
81
+
82
+ # 5: Add winipedia-utils to your project
69
83
  poetry add winipedia-utils
70
84
 
71
- # Run the automated setup
85
+ # 6: Run the automated setup
72
86
  poetry run python -m winipedia_utils.setup
73
87
  ```
74
88
 
@@ -88,8 +102,9 @@ The setup creates the following configuration files:
88
102
  - `.pre-commit-config.yaml` - Pre-commit hook configuration
89
103
  - `.gitignore` - Git ignore rules (assumes you added one on GitHub before.)
90
104
  - `pyproject.toml` - Project configuration with Poetry settings
91
- - `.github/workflows/release.yaml` - Release workflow (Creates a release on GitHub when oushing to main)
92
- - `.github/workflows/publish.yaml` - Publishing workflow (Publishes to PyPI when a release is created by the release workflow)
105
+ - `.github/workflows/health_check.yaml` - Health check workflow (Runs on every push and pull request)
106
+ - `.github/workflows/release.yaml` - Release workflow (Creates a release on GitHub when the same actions as in health check pass and commits are pushed to main)
107
+ - `.github/workflows/publish.yaml` - Publishing workflow (Publishes to PyPI when a release is created by the release workflow, if you use this workflow, you need to add a PYPI_TOKEN (named PYPI_TOKEN) to your GitHub secrets that has write access to the package on PyPI.)
93
108
  - `py.typed` - PEP 561 marker for type hints
94
109
  - `experiment.py` - For experimentation (ignored by git)
95
110
  - `test0.py` - Test file with one empyt test (so that initial tests pass)
@@ -106,8 +121,8 @@ Usually VSCode or other IDEs activates the venv automatically when opening the t
106
121
  1. Patch version (poetry version patch)
107
122
  2. Add version patch to git (git add pyproject.toml)
108
123
  3. Update package manager (poetry self update)
109
- 4. Install packages (poetry install)
110
- 5. Update packages (poetry update)
124
+ 4. Install packages (poetry install --with dev)
125
+ 5. Update packages (poetry update --with dev (winipedia_utils forces all dependencies with * to be updated to latest compatible version))
111
126
  6. Lock dependencies (poetry lock)
112
127
  7. Check package manager configs (poetry check --strict)
113
128
  8. Create tests (python -m winipedia_utils.testing.create_tests)