checkup-python 0.2.0__tar.gz
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.
- checkup_python-0.2.0/.gitignore +15 -0
- checkup_python-0.2.0/PKG-INFO +6 -0
- checkup_python-0.2.0/pyproject.toml +18 -0
- checkup_python-0.2.0/src/checkup_python/__init__.py +4 -0
- checkup_python-0.2.0/src/checkup_python/metrics/__init__.py +3 -0
- checkup_python-0.2.0/src/checkup_python/metrics/utils.py +7 -0
- checkup_python-0.2.0/src/checkup_python/metrics/version.py +112 -0
- checkup_python-0.2.0/src/checkup_python/metrics/version_check.py +36 -0
- checkup_python-0.2.0/tests/checkup-test/tests/__init__.py +0 -0
- checkup_python-0.2.0/tests/fixtures/test_config.yaml +4 -0
- checkup_python-0.2.0/tests/test_python_hub.py +18 -0
- checkup_python-0.2.0/tests/test_python_metric.py +21 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "checkup-python"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Python metrics for checkup"
|
|
5
|
+
requires-python = ">=3.12"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"checkup",
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
[tool.uv.sources]
|
|
11
|
+
checkup = { workspace = true }
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["hatchling"]
|
|
15
|
+
build-backend = "hatchling.build"
|
|
16
|
+
|
|
17
|
+
[tool.hatch.build.targets.wheel]
|
|
18
|
+
packages = ["src/checkup_python"]
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import ClassVar
|
|
5
|
+
|
|
6
|
+
from checkup.metric import Metric
|
|
7
|
+
from checkup.types import Context
|
|
8
|
+
from checkup_python.metrics.utils import parse_semantic_version
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PythonVersionMetric(Metric):
|
|
12
|
+
"""
|
|
13
|
+
Metric to detect the Python version configured for a project.
|
|
14
|
+
|
|
15
|
+
Checks (in order):
|
|
16
|
+
1. .python-version file
|
|
17
|
+
2. pyproject.toml
|
|
18
|
+
3. Falls back to current runtime version
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
name: ClassVar[str] = "python_version"
|
|
22
|
+
description: ClassVar[str] = "The Python version configured for the project"
|
|
23
|
+
unit: ClassVar[str] = "version"
|
|
24
|
+
|
|
25
|
+
def calculate(self, context: Context, metrics: dict[type[Metric], Metric]) -> None:
|
|
26
|
+
path = None
|
|
27
|
+
|
|
28
|
+
if "path" in context:
|
|
29
|
+
path = Path(context["path"])
|
|
30
|
+
else:
|
|
31
|
+
path = Path.cwd()
|
|
32
|
+
|
|
33
|
+
version = (
|
|
34
|
+
self._read_python_version_file(path)
|
|
35
|
+
or self._read_pyproject_toml(path)
|
|
36
|
+
or self._get_runtime_version()
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
self.value = version
|
|
40
|
+
|
|
41
|
+
def _read_python_version_file(self, path: Path) -> str | None:
|
|
42
|
+
"""
|
|
43
|
+
Read version from `.python-version`.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
version_file = path / ".python-version"
|
|
47
|
+
if not version_file.exists():
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
version_file.read_text().strip().removeprefix("python-").removeprefix("py")
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
def _read_pyproject_toml(self, path: Path) -> str | None:
|
|
55
|
+
"""
|
|
56
|
+
Extract Python version from `pyproject.toml`.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
pyproject_file = path / "pyproject.toml"
|
|
60
|
+
if not pyproject_file.exists():
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
content = pyproject_file.read_text()
|
|
64
|
+
# Look for requires-python = ">=3.11" or similar
|
|
65
|
+
match = re.search(r'requires-python\s*=\s*["\']([^"\']+)["\']', content)
|
|
66
|
+
if not match:
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
version_spec = match.group(1)
|
|
70
|
+
# Extract version number from spec like ">=3.11", "^3.11", "~=3.11.0"
|
|
71
|
+
version_match = re.search(r"(\d+\.\d+(?:\.\d+)?)", version_spec)
|
|
72
|
+
if not version_match:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
return version_match.group(1)
|
|
76
|
+
|
|
77
|
+
def _get_runtime_version(self) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Get the current runtime Python version.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
return f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
83
|
+
|
|
84
|
+
def __lt__(self, other) -> bool:
|
|
85
|
+
if not isinstance(other, PythonVersionMetric):
|
|
86
|
+
return NotImplemented
|
|
87
|
+
return parse_semantic_version(self.value) < parse_semantic_version(other.value)
|
|
88
|
+
|
|
89
|
+
def __le__(self, other) -> bool:
|
|
90
|
+
if not isinstance(other, PythonVersionMetric):
|
|
91
|
+
return NotImplemented
|
|
92
|
+
return parse_semantic_version(self.value) <= parse_semantic_version(other.value)
|
|
93
|
+
|
|
94
|
+
def __gt__(self, other) -> bool:
|
|
95
|
+
if not isinstance(other, PythonVersionMetric):
|
|
96
|
+
return NotImplemented
|
|
97
|
+
return parse_semantic_version(self.value) > parse_semantic_version(other.value)
|
|
98
|
+
|
|
99
|
+
def __ge__(self, other) -> bool:
|
|
100
|
+
if not isinstance(other, PythonVersionMetric):
|
|
101
|
+
return NotImplemented
|
|
102
|
+
return parse_semantic_version(self.value) >= parse_semantic_version(other.value)
|
|
103
|
+
|
|
104
|
+
def __eq__(self, other) -> bool:
|
|
105
|
+
if not isinstance(other, PythonVersionMetric):
|
|
106
|
+
return NotImplemented
|
|
107
|
+
return parse_semantic_version(self.value) == parse_semantic_version(other.value)
|
|
108
|
+
|
|
109
|
+
def __ne__(self, other) -> bool:
|
|
110
|
+
if not isinstance(other, PythonVersionMetric):
|
|
111
|
+
return NotImplemented
|
|
112
|
+
return parse_semantic_version(self.value) != parse_semantic_version(other.value)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from typing import ClassVar
|
|
2
|
+
|
|
3
|
+
from checkup.metric import Metric
|
|
4
|
+
from checkup.types import Context
|
|
5
|
+
from checkup_python.metrics.utils import parse_semantic_version
|
|
6
|
+
from checkup_python.metrics.version import PythonVersionMetric
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PythonVersionCheckMetric(Metric):
|
|
10
|
+
"""
|
|
11
|
+
Metric that checks the Python version of the current project
|
|
12
|
+
and compares it to given thresholds.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
name: ClassVar[str] = "python_version_check"
|
|
16
|
+
description: ClassVar[str] = (
|
|
17
|
+
"The Python version adheres to a minimum and maximum boundary"
|
|
18
|
+
)
|
|
19
|
+
unit: ClassVar[str] = "bool"
|
|
20
|
+
|
|
21
|
+
min_version: str
|
|
22
|
+
max_version: str
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def depends_on(cls) -> list[type[Metric]]:
|
|
26
|
+
return [PythonVersionMetric]
|
|
27
|
+
|
|
28
|
+
def calculate(self, context: Context, metrics: dict[type[Metric], Metric]) -> None:
|
|
29
|
+
version_metric = metrics[PythonVersionMetric]
|
|
30
|
+
actual_version = version_metric.value
|
|
31
|
+
|
|
32
|
+
actual = parse_semantic_version(actual_version)
|
|
33
|
+
min_ver = parse_semantic_version(self.min_version)
|
|
34
|
+
max_ver = parse_semantic_version(self.max_version)
|
|
35
|
+
|
|
36
|
+
self.value = min_ver <= actual <= max_ver
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from checkup_python.metrics.version_check import PythonVersionCheckMetric
|
|
4
|
+
|
|
5
|
+
from checkup.hub import CheckHub
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_measure() -> None:
|
|
9
|
+
config_path = Path(__file__).parent / "fixtures" / "test_config.yaml"
|
|
10
|
+
|
|
11
|
+
result = (
|
|
12
|
+
CheckHub(config_path=config_path)
|
|
13
|
+
.with_metrics([PythonVersionCheckMetric])
|
|
14
|
+
.measure()
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# `PythonVersionCheckMetric` depends on `PythonVersionMetric`
|
|
18
|
+
assert len(result.metrics) == 2
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from checkup_python.metrics import PythonVersionMetric
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_python_version_metric_calculate() -> None:
|
|
5
|
+
metric = PythonVersionMetric()
|
|
6
|
+
metric.calculate(context={}, metrics={})
|
|
7
|
+
|
|
8
|
+
assert metric.value is not None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_python_version_metric_compare() -> None:
|
|
12
|
+
metric1 = PythonVersionMetric()
|
|
13
|
+
metric1.value = "3.8.10"
|
|
14
|
+
|
|
15
|
+
metric2 = PythonVersionMetric()
|
|
16
|
+
metric2.value = "3.9"
|
|
17
|
+
|
|
18
|
+
assert metric1 < metric2
|
|
19
|
+
assert metric2 > metric1
|
|
20
|
+
assert metric1 != metric2
|
|
21
|
+
assert not (metric1 == metric2)
|