Skip to content

Commit 993ec1d

Browse files
authored
Merge pull request #48 from xfenix/codex/migrate-to-local-badges-generation
ci: replace codecov with local coverage badge
2 parents cd45a99 + 6b7ec4f commit 993ec1d

File tree

5 files changed

+164
-21
lines changed

5 files changed

+164
-21
lines changed

.github/badges/coverage.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"schemaVersion": 1, "label": "coverage", "message": "100%", "color": "#2A9D8F"}

.github/workflows/pipeline.yml

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ jobs:
2727
with:
2828
failure-threshold: error
2929

30-
submit-coverage:
30+
coverage:
3131
runs-on: ubuntu-latest
32+
permissions:
33+
contents: write
34+
pages: write
35+
id-token: write
3236
container:
3337
image: pypy:3.11-slim
3438
steps:
@@ -38,16 +42,33 @@ jobs:
3842
apt-get install -y enchant-2 hunspell-ru hunspell-es hunspell-de-de hunspell-fr hunspell-pt-pt curl
3943
pip install uv
4044
uv sync --group dev
41-
uv run pytest -n3 --cov-report=xml
42-
- name: Upload coverage reports to Codecov
43-
uses: codecov/codecov-action@v3
44-
env:
45-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
46-
files: coverage.xml
45+
- run: uv run pytest -n3 . --cov-report=xml --cov-report=html
46+
- run: uv run python -m scripts build-coverage-badge
47+
- uses: stefanzweifel/git-auto-commit-action@v5
48+
with:
49+
commit_message: "docs: update coverage badge"
50+
file_pattern: .github/badges/coverage.json
51+
- run: mv htmlcov coverage
52+
- uses: actions/upload-pages-artifact@v3
53+
with:
54+
path: coverage
55+
56+
deploy-coverage:
57+
needs: coverage
58+
runs-on: ubuntu-latest
59+
permissions:
60+
pages: write
61+
id-token: write
62+
environment:
63+
name: github-pages
64+
url: ${{ steps.deployment.outputs.page_url }}
65+
steps:
66+
- id: deployment
67+
uses: actions/deploy-pages@v4
4768

4869
# build stage with auto-versioning based on git tags like vX.Y.Z (example: v3.1.2)
4970
build-and-publish:
50-
needs: [py-lint-and-test, docker-lint, submit-coverage]
71+
needs: [py-lint-and-test, docker-lint, coverage]
5172
runs-on: ubuntu-latest
5273
if: startsWith(github.ref, 'refs/tags/v')
5374
steps:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Spellcheck microservice
22
[![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/xfenix/spellcheck-microservice?label=version)](https://github.com/xfenix/spellcheck-microservice/releases)
33
[![Docker Pulls](https://img.shields.io/docker/pulls/xfenix/spellcheck-microservice)](https://hub.docker.com/r/xfenix/spellcheck-microservice)
4-
[![codecov](https://codecov.io/gh/xfenix/spellcheck-microservice/graph/badge.svg?token=IyBXLeKWae)](https://codecov.io/gh/xfenix/spellcheck-microservice)
4+
[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/xfenix/spellcheck-microservice/main/.github/badges/coverage.json)](https://xfenix.github.io/spellcheck-microservice/coverage/)
55
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
66
<a href="https://github.com/psf/black" target="_blank"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
77
[![Imports: isort](https://img.shields.io/badge/imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://timothycrosley.github.io/isort/)

scripts/__main__.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,28 @@
22
"""Simple dockerhub readme generator."""
33

44
import argparse
5+
import contextlib
6+
import json
57
import pathlib
8+
import random
69
import re
710
import sys
11+
import time
12+
import types
813
import typing
14+
import xml.etree.ElementTree as ET
915

1016
from ._helpers import parse_last_git_tag, replace_tag_in_readme
1117
from whole_app.settings import SETTINGS
1218

1319

1420
PARENT_DIR: typing.Final = pathlib.Path(__file__).parent.parent
1521
README_PATH: typing.Final = PARENT_DIR / "README.md"
22+
COVERAGE_XML_PATH: typing.Final = pathlib.Path("coverage.xml")
23+
BADGE_JSON_PATH: typing.Final = pathlib.Path(".github/badges/coverage.json")
24+
LOW_BOUNDARY: typing.Final[float] = 60
25+
HIGH_BOUNDARY: typing.Final[float] = 80
26+
RETRY_ATTEMPTS: typing.Final[int] = 3
1627

1728

1829
def _update_dockerhub_readme() -> None:
@@ -65,6 +76,55 @@ def _update_readme() -> None:
6576
README_PATH.write_text(new_content)
6677

6778

79+
def _fetch_xml_text() -> str:
80+
for _attempt_index in range(RETRY_ATTEMPTS):
81+
with contextlib.suppress(OSError):
82+
return COVERAGE_XML_PATH.read_text()
83+
time.sleep(random.uniform(0.1, 0.3)) # noqa: S311
84+
error_message: typing.Final = f"Failed to read {COVERAGE_XML_PATH} after {RETRY_ATTEMPTS} attempts"
85+
raise OSError(error_message)
86+
87+
88+
def _persist_badge_text(badge_text: str) -> None:
89+
BADGE_JSON_PATH.parent.mkdir(parents=True, exist_ok=True)
90+
for _attempt_index in range(RETRY_ATTEMPTS):
91+
with contextlib.suppress(OSError):
92+
BADGE_JSON_PATH.write_text(badge_text)
93+
return
94+
time.sleep(random.uniform(0.1, 0.3)) # noqa: S311
95+
error_message: typing.Final = f"Failed to write {BADGE_JSON_PATH} after {RETRY_ATTEMPTS} attempts"
96+
raise OSError(error_message)
97+
98+
99+
def _build_coverage_badge() -> None:
100+
xml_source_text: typing.Final[str] = _fetch_xml_text()
101+
root_element: typing.Final[ET.Element] = ET.fromstring(xml_source_text) # noqa: S314
102+
line_rate_text: typing.Final[str | None] = root_element.attrib.get("line-rate")
103+
if line_rate_text is None:
104+
missing_attr_message: typing.Final[str] = "Missing 'line-rate' attribute in coverage report"
105+
raise KeyError(missing_attr_message)
106+
coverage_percent: typing.Final[float] = float(line_rate_text) * 100.0
107+
108+
message_text: typing.Final[str] = f"{coverage_percent:.0f}%"
109+
color_text: str
110+
if coverage_percent < LOW_BOUNDARY:
111+
color_text = "#E63946"
112+
elif coverage_percent < HIGH_BOUNDARY:
113+
color_text = "#FFB347"
114+
else:
115+
color_text = "#2A9D8F"
116+
117+
badge_mapping: typing.Final[typing.Mapping[str, typing.Any]] = types.MappingProxyType(
118+
{
119+
"schemaVersion": 1,
120+
"label": "coverage",
121+
"message": message_text,
122+
"color": color_text,
123+
},
124+
)
125+
_persist_badge_text(json.dumps(dict(badge_mapping)))
126+
127+
68128
if __name__ == "__main__":
69129
sys.path.append(str(PARENT_DIR.resolve()))
70130

@@ -76,5 +136,7 @@ def _update_readme() -> None:
76136
_update_dockerhub_readme()
77137
case "update-readme":
78138
_update_readme()
139+
case "build-coverage-badge":
140+
_build_coverage_badge()
79141
case _:
80142
print("Unknown action") # noqa: T201

0 commit comments

Comments
 (0)