winipedia-utils 0.4.33__py3-none-any.whl → 0.4.38__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.

@@ -0,0 +1,31 @@
1
+ """GitHub utilities for working with GitHub repositories."""
2
+
3
+ import os
4
+
5
+ from winipedia_utils.text.config import DotEnvConfigFile
6
+
7
+
8
+ def get_github_repo_token() -> str:
9
+ """Get the GitHub token."""
10
+ # try os env first
11
+ token = os.getenv("REPO_TOKEN")
12
+ if token:
13
+ return token
14
+
15
+ # try .env next
16
+ dotenv_path = DotEnvConfigFile.get_path()
17
+ if not dotenv_path.exists():
18
+ msg = f"Expected {dotenv_path} to exist"
19
+ raise ValueError(msg)
20
+ dotenv = DotEnvConfigFile.load()
21
+ token = dotenv.get("REPO_TOKEN")
22
+ if token:
23
+ return token
24
+
25
+ msg = f"Expected REPO_TOKEN in {dotenv_path}"
26
+ raise ValueError(msg)
27
+
28
+
29
+ def running_in_github_action() -> bool:
30
+ """Check if we are running in a GitHub action."""
31
+ return os.getenv("GITHUB_ACTIONS", "false") == "true"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
+ from winipedia_utils.git.github.github import get_github_repo_token
5
6
  from winipedia_utils.git.github.repo.repo import (
6
7
  DEFAULT_BRANCH,
7
8
  DEFAULT_RULESET_NAME,
@@ -12,7 +13,6 @@ from winipedia_utils.git.github.repo.repo import (
12
13
  from winipedia_utils.git.github.workflows.health_check import HealthCheckWorkflow
13
14
  from winipedia_utils.modules.package import get_src_package
14
15
  from winipedia_utils.projects.poetry.config import PyprojectConfigFile
15
- from winipedia_utils.testing.tests.base.utils.utils import get_github_repo_token
16
16
 
17
17
 
18
18
  def protect_repository() -> None:
@@ -7,6 +7,7 @@ from typing import Any
7
7
  import winipedia_utils
8
8
  from winipedia_utils.modules.module import make_obj_importpath
9
9
  from winipedia_utils.modules.package import get_src_package
10
+ from winipedia_utils.projects.poetry.config import PyprojectConfigFile
10
11
  from winipedia_utils.text.config import YamlConfigFile
11
12
  from winipedia_utils.text.string import split_on_uppercase
12
13
 
@@ -149,7 +150,9 @@ class Workflow(YamlConfigFile):
149
150
  {
150
151
  "name": "Setup Python",
151
152
  "uses": "actions/setup-python@main",
152
- "with": {"python-version": "3.x"},
153
+ "with": {
154
+ "python-version": PyprojectConfigFile.get_latest_possible_python_version() # noqa: E501
155
+ },
153
156
  }
154
157
  )
155
158
  steps.append(
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
  from typing import Any, cast
5
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
7
8
  from winipedia_utils.testing.convention import TESTS_PACKAGE_NAME
8
9
  from winipedia_utils.text.config import ConfigFile, TomlConfigFile
9
10
 
@@ -130,6 +131,35 @@ class PyprojectConfigFile(TomlConfigFile):
130
131
  """Get the main author name."""
131
132
  return cls.get_main_author()["name"]
132
133
 
134
+ @classmethod
135
+ def get_latest_possible_python_version(cls) -> str:
136
+ """Get the latest possible python version."""
137
+ constraint = cls.load()["project"]["requires-python"]
138
+ 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"
151
+
152
+ @classmethod
153
+ def get_first_supported_python_version(cls) -> str:
154
+ """Get the first supported python version."""
155
+ constraint = cls.load()["project"]["requires-python"]
156
+ version_constraint = VersionConstraint(constraint)
157
+ lower = version_constraint.get_lower_inclusive()
158
+ if lower is None:
159
+ msg = "Need a lower bound for python version"
160
+ raise ValueError(msg)
161
+ return str(lower)
162
+
133
163
 
134
164
  class TypedConfigFile(ConfigFile):
135
165
  """Config file for py.typed."""
@@ -164,3 +194,44 @@ class PyTypedConfigFile(ConfigFile):
164
194
  def get_parent_path(cls) -> Path:
165
195
  """Get the path to the config file."""
166
196
  return Path(PyprojectConfigFile.get_package_name())
197
+
198
+
199
+ class DotPythonVersionConfigFile(ConfigFile):
200
+ """Config file for .python-version."""
201
+
202
+ VERSION_KEY = "version"
203
+
204
+ @classmethod
205
+ def get_filename(cls) -> str:
206
+ """Get the filename of the config file."""
207
+ return "" # so it builds the path .python-version
208
+
209
+ @classmethod
210
+ def get_file_extension(cls) -> str:
211
+ """Get the file extension of the config file."""
212
+ return "python-version"
213
+
214
+ @classmethod
215
+ def get_parent_path(cls) -> Path:
216
+ """Get the path to the config file."""
217
+ return Path()
218
+
219
+ @classmethod
220
+ def get_configs(cls) -> dict[str, Any]:
221
+ """Get the config."""
222
+ return {
223
+ cls.VERSION_KEY: PyprojectConfigFile.get_first_supported_python_version()
224
+ }
225
+
226
+ @classmethod
227
+ def load(cls) -> dict[str, Any]:
228
+ """Load the config file."""
229
+ return {cls.VERSION_KEY: cls.get_path().read_text()}
230
+
231
+ @classmethod
232
+ def dump(cls, config: dict[str, Any] | list[Any]) -> None:
233
+ """Dump the config file."""
234
+ if not isinstance(config, dict):
235
+ msg = f"Cannot dump {config} to .python-version file."
236
+ raise TypeError(msg)
237
+ cls.get_path().write_text(config[cls.VERSION_KEY])
@@ -6,6 +6,9 @@ This module provides utility functions for working with Python projects
6
6
  from collections.abc import Iterable
7
7
  from types import ModuleType
8
8
 
9
+ from packaging.specifiers import SpecifierSet
10
+ from packaging.version import Version
11
+
9
12
  from winipedia_utils.logging.logger import get_logger
10
13
 
11
14
  logger = get_logger(__name__)
@@ -40,3 +43,87 @@ def get_python_module_script(module: ModuleType) -> str:
40
43
  def get_poetry_run_module_script(module: ModuleType) -> str:
41
44
  """Get the script to run a module."""
42
45
  return get_script_from_args([*POETRY_RUN_ARGS, *get_run_python_module_args(module)])
46
+
47
+
48
+ class VersionConstraint:
49
+ """Version class."""
50
+
51
+ def __init__(self, constraint: str) -> None:
52
+ """Initialize the version."""
53
+ self.constraint = constraint
54
+ self.spec = self.constraint.strip().strip('"').strip("'")
55
+ self.sset = SpecifierSet(self.spec)
56
+
57
+ self.lowers_inclusive = [
58
+ Version(s.version) for s in self.sset if s.operator == ">="
59
+ ]
60
+ self.lowers_exclusive = [
61
+ Version(s.version) for s in self.sset if s.operator == ">"
62
+ ]
63
+ # increment the last number of exclusive, so
64
+ # >3.4.1 to >=3.4.2; <3.4.0 to <=3.4.1; 3.0.0 to <=3.0.1
65
+ self.lowers_exclusive_to_inclusive = [
66
+ Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.lowers_exclusive
67
+ ]
68
+ self.lowers_inclusive = (
69
+ self.lowers_inclusive + self.lowers_exclusive_to_inclusive
70
+ )
71
+
72
+ self.uppers_inclusive = [
73
+ Version(s.version) for s in self.sset if s.operator == "<="
74
+ ]
75
+ self.uppers_exclusive = [
76
+ Version(s.version) for s in self.sset if s.operator == "<"
77
+ ]
78
+
79
+ # increment the last number of inclusive, so
80
+ # <=3.4.1 to <3.4.2; >=3.4.0 to >3.4.1; 3.0.0 to >3.0.1
81
+ self.uppers_inclusive_to_exclusive = [
82
+ Version(f"{v.major}.{v.minor}.{v.micro + 1}") for v in self.uppers_inclusive
83
+ ]
84
+ self.uppers_exclusive = (
85
+ self.uppers_inclusive_to_exclusive + self.uppers_exclusive
86
+ )
87
+
88
+ self.upper_exclusive = (
89
+ min(self.uppers_exclusive) if self.uppers_exclusive else None
90
+ )
91
+ self.lower_inclusive = (
92
+ max(self.lowers_inclusive) if self.lowers_inclusive else None
93
+ )
94
+
95
+ def get_lower_inclusive(self, default: str | None = None) -> Version | None:
96
+ """Get the minimum version.
97
+
98
+ Is given inclusive. E.g. >=3.8, <3.12 -> 3.8
99
+ if >3.7, <3.12 -> 3.7.1
100
+
101
+ E.g. >=3.8, <3.12 -> 3.8
102
+
103
+ Args:
104
+ default: The default value to return if there is no minimum version
105
+
106
+ Returns:
107
+ The minimum version
108
+ """
109
+ if self.lower_inclusive is None:
110
+ return Version(default) if default else None
111
+
112
+ return self.lower_inclusive
113
+
114
+ def get_upper_exclusive(self, default: str | None = None) -> Version | None:
115
+ """Get the maximum version.
116
+
117
+ Is given exclusive. E.g. >=3.8, <3.12 -> 3.12
118
+ if >=3.8, <=3.12 -> 3.12.1
119
+
120
+ Args:
121
+ default: The default value to return if there is no maximum version
122
+
123
+ Returns:
124
+ The maximum version
125
+ """
126
+ if self.upper_exclusive is None:
127
+ return Version(default) if default else None
128
+
129
+ return self.upper_exclusive
@@ -4,7 +4,16 @@ import functools
4
4
 
5
5
  import pytest
6
6
 
7
+ from winipedia_utils.git.github.github import running_in_github_action
8
+
7
9
  skip_fixture_test: pytest.MarkDecorator = functools.partial(
8
10
  pytest.mark.skip,
9
11
  reason="Fixtures are not testable bc they cannot be called directly.",
10
12
  )()
13
+
14
+
15
+ skip_in_github_actions: pytest.MarkDecorator = functools.partial(
16
+ pytest.mark.skipif,
17
+ running_in_github_action(),
18
+ reason="Test cannot run in GitHub action.",
19
+ )()
@@ -9,7 +9,6 @@ Returns:
9
9
 
10
10
  """
11
11
 
12
- import os
13
12
  from collections.abc import Callable
14
13
  from types import ModuleType
15
14
  from typing import Any
@@ -26,7 +25,6 @@ from winipedia_utils.testing.convention import (
26
25
  make_test_obj_importpath_from_obj,
27
26
  make_untested_summary_error_msg,
28
27
  )
29
- from winipedia_utils.text.config import DotEnvConfigFile
30
28
 
31
29
  logger = get_logger(__name__)
32
30
 
@@ -81,24 +79,3 @@ def assert_isabstrct_method(method: Any) -> None:
81
79
  is_abstractmethod(method),
82
80
  f"Expected {method} to be abstract method",
83
81
  )
84
-
85
-
86
- def get_github_repo_token() -> str:
87
- """Get the GitHub token."""
88
- # try os env first
89
- token = os.getenv("REPO_TOKEN")
90
- if token:
91
- return token
92
-
93
- # try .env next
94
- dotenv_path = DotEnvConfigFile.get_path()
95
- if not dotenv_path.exists():
96
- msg = f"Expected {dotenv_path} to exist"
97
- raise ValueError(msg)
98
- dotenv = DotEnvConfigFile.load()
99
- token = dotenv.get("REPO_TOKEN")
100
- if token:
101
- return token
102
-
103
- msg = f"Expected REPO_TOKEN in {dotenv_path}"
104
- raise ValueError(msg)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: winipedia-utils
3
- Version: 0.4.33
3
+ Version: 0.4.38
4
4
  Summary: A package with many utility functions
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -10,12 +10,13 @@ winipedia_utils/data/structures/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9
10
10
  winipedia_utils/data/structures/dicts.py,sha256=jsFzQ96cvyHsvPSnsEUhksuWvLSGq6-Rryfw6gEXq-c,274
11
11
  winipedia_utils/git/__init__.py,sha256=IRmEVz0sUEw47Eli--57YaypWitxlcYThT_ulwkhNTE,47
12
12
  winipedia_utils/git/github/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
13
+ winipedia_utils/git/github/github.py,sha256=xjV7s-ve1s6TJ2GMxm0pqoJkuJ5wECiHwKfhz5DKiUc,813
13
14
  winipedia_utils/git/github/repo/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
14
- winipedia_utils/git/github/repo/protect.py,sha256=_BtYRtGrZPPyR_CuFGkoPugCvuDib4inpnF09Y9NIm0,3164
15
+ winipedia_utils/git/github/repo/protect.py,sha256=nOVjb5GVinGIClp7k9_qqgKnAl_gk1XwkViAQ5TaLTo,3151
15
16
  winipedia_utils/git/github/repo/repo.py,sha256=OqoOfqDhe_Iik71dNqi4h3fGrMno33hSjk0bpNg3eZk,7865
16
17
  winipedia_utils/git/github/workflows/__init__.py,sha256=BPdntTwFEyBMJ6MyT7gddPHswvRdH9tsRtfK72VSV7Y,57
17
18
  winipedia_utils/git/github/workflows/base/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
18
- winipedia_utils/git/github/workflows/base/base.py,sha256=6QyRPUHaTjHOLcA3Le2qffcMEzjvI0wZMOc9y2MIGr0,9575
19
+ winipedia_utils/git/github/workflows/base/base.py,sha256=t2jYHVpvhCaKpaZI9Yhr8YJEp-2D_G4VUJKVVYIUolc,9749
19
20
  winipedia_utils/git/github/workflows/health_check.py,sha256=cPjSJxNy9h0bKNK69crmcERACx4VGR2XytyZ1JM_wMQ,1567
20
21
  winipedia_utils/git/github/workflows/publish.py,sha256=TPbSp5QH2vVl55UdqE_kjf1HIkdubcgqWlkLjWFX5EA,1378
21
22
  winipedia_utils/git/github/workflows/release.py,sha256=hnMT12J17LmFpPCTzva-lW95MOFDQ-1bAOrTVCxeaR8,1301
@@ -45,8 +46,8 @@ winipedia_utils/os/__init__.py,sha256=cBRq8hWhaWvYeC3cSBYL6Y70kM9COQWHj8vVxxSadI
45
46
  winipedia_utils/os/os.py,sha256=K_5FD1sC1h5aSdtqXAG0uq90sSweLYLkgkRPQS0Jfxg,1768
46
47
  winipedia_utils/projects/__init__.py,sha256=_iYHzUcTPmutpsExPDcMF9OQDgnz-kTSuWens9iP9bI,52
47
48
  winipedia_utils/projects/poetry/__init__.py,sha256=tbvV3wYd3H39hjjlKbF84Irj4hYgv1A7KWyXdCQzFro,59
48
- winipedia_utils/projects/poetry/config.py,sha256=dei-jinjcwme6oAMO1Wxk7_O7Ac6s0g6PXBglfsTvcw,5296
49
- winipedia_utils/projects/poetry/poetry.py,sha256=JC-N7NjYCHxUSCGbdpvQltJDabfvTIKLgd62d9XhACo,1174
49
+ winipedia_utils/projects/poetry/config.py,sha256=aqMBG36hWB5XK9Z1484JEFRUsM5HkPgMdJ8EoKtZ3p8,7762
50
+ winipedia_utils/projects/poetry/poetry.py,sha256=CM-MKuYSITRIS95qngtCpDpjQS6tCe6joZOM03yEFdU,4033
50
51
  winipedia_utils/projects/project.py,sha256=rirg4xCIOTI6w7cLWufAVHAix7FGicvaCd9OnZQP8dA,521
51
52
  winipedia_utils/py.typed,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
52
53
  winipedia_utils/resources/__init__.py,sha256=XHsbmjiaGom-KX-S3leCY9cJD3aP9p_0X6xYMcdkHBU,23
@@ -70,7 +71,7 @@ winipedia_utils/testing/config.py,sha256=Xtgu7Nsll6pIzHLUDsalzt-nPBNSNOs0JKCXWi5
70
71
  winipedia_utils/testing/convention.py,sha256=7vurpqS--awhN_FLSOviSKENGuFyY9Ejr1NKRm0MPsg,4833
71
72
  winipedia_utils/testing/create_tests.py,sha256=KJL23BtsnDsujUa9fwBA5AHo85vy61oxgRPSTztJT7k,9347
72
73
  winipedia_utils/testing/fixtures.py,sha256=G8QIrZXndtud0uOk6PY3f8IIDoyX_ronogjeLAYGjrM,1033
73
- winipedia_utils/testing/skip.py,sha256=5AdF7naPkBi4B1AF6b1plJF3Wmb-BzGBvUORbHZTo8c,236
74
+ winipedia_utils/testing/skip.py,sha256=vAsaIFjxhBhay2ykTqSwjfrJF6Hl2VwT6dQgKxTNRXM,484
74
75
  winipedia_utils/testing/tests/__init__.py,sha256=kL-1O6lAO5j4JPOqPdi3dHdbOQ_UXcgPFppj82HhrRU,57
75
76
  winipedia_utils/testing/tests/base/__init__.py,sha256=dBH1yfONmqUC49zshS6BJ4ZgEcw7iFGoFCqRmU7Vhrw,62
76
77
  winipedia_utils/testing/tests/base/fixtures/__init__.py,sha256=Bs_HSqx8DcMb8yU1vNbgIEszOMNVnIW02C4tmDdHB8E,71
@@ -82,12 +83,12 @@ winipedia_utils/testing/tests/base/fixtures/scopes/module.py,sha256=eWxUP9cFJ0kH
82
83
  winipedia_utils/testing/tests/base/fixtures/scopes/package.py,sha256=pR3so6QPymIRM4PJTODrlBKI-yQnZ2P78BsiyTPaF8o,302
83
84
  winipedia_utils/testing/tests/base/fixtures/scopes/session.py,sha256=EcBqu3iwT7397wDxFCY-NkiaH7JPVJDdXVUTGXClWJc,6108
84
85
  winipedia_utils/testing/tests/base/utils/__init__.py,sha256=mC-8dCkp8xarqkQu2QQLrPjHi6Ww9hcixWdHeQHWeRs,68
85
- winipedia_utils/testing/tests/base/utils/utils.py,sha256=qN6SiSVPk3Cr-WMsvtSyyULeksocWiGqjEfDRzzcGfA,3194
86
+ winipedia_utils/testing/tests/base/utils/utils.py,sha256=D7N-PW4N8853nJ2m4eYjO3jBMByYB9oh1GK4Hl5Tbwg,2598
86
87
  winipedia_utils/testing/tests/conftest.py,sha256=BLgUJtLecOwuEsIyJ__0buqovd5AhiGvbMNk8CHgSQs,888
87
88
  winipedia_utils/text/__init__.py,sha256=j2bwtK6kyeHI6SnoBjpRju0C1W2n2paXBDlNjNtaUxA,48
88
89
  winipedia_utils/text/config.py,sha256=lt5QIXS2cY_aS0emZztcG6nRYwiqFKe0lGEq6msMF5E,7434
89
90
  winipedia_utils/text/string.py,sha256=yXmwOab5hXyVQG1NwlWDpy2prj0U7Vb2F5HKLT2Y77Q,3382
90
- winipedia_utils-0.4.33.dist-info/METADATA,sha256=Js4VMX8o1DB7mTF_pfcyb3sT4dbeUQDI49fo1r-EzZc,13344
91
- winipedia_utils-0.4.33.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
92
- winipedia_utils-0.4.33.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
93
- winipedia_utils-0.4.33.dist-info/RECORD,,
91
+ winipedia_utils-0.4.38.dist-info/METADATA,sha256=zZ1EEyURgkEq7w5X_A6zikGQbdc2psG5W7GZZg_WjzU,13344
92
+ winipedia_utils-0.4.38.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
93
+ winipedia_utils-0.4.38.dist-info/licenses/LICENSE,sha256=o316mE2gGzd__JT69p7S_zlOmKiHh8YjpImCCcWyTvM,1066
94
+ winipedia_utils-0.4.38.dist-info/RECORD,,