Skip to content

Commit d03dbc8

Browse files
committed
feat: add run_checks script and improve scheduling logic for display
1 parent 6d75cf8 commit d03dbc8

6 files changed

Lines changed: 74 additions & 10 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ build-backend = "uv_build"
3232

3333
[project.scripts]
3434
sample-python-app = "sample_python_app.main:run_app"
35+
checks = "sample_python_app.scripts:run_checks"
3536

3637
[tool.semantic_release]
3738
build_command = """

scripts/checks.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env bash
2+
# Run project linting, formatting and tests using `uv` as in this repo.
3+
set -euo pipefail
4+
5+
# Change to repo root (script placed in scripts/)
6+
cd "$(dirname "$0")/.."
7+
8+
echo "⚡ Running ruff (auto-fix where possible)"
9+
uv run ruff check . --fix
10+
11+
echo "⚡ Running isort"
12+
uv run isort .
13+
14+
echo "⚡ Running black"
15+
uv run black .
16+
17+
echo "⚡ Re-running ruff to catch any remaining issues"
18+
uv run ruff check .
19+
20+
echo "⚡ Running tests"
21+
uv run -- coverage run -m pytest
22+
23+
echo "All checks completed."

src/sample_python_app/app/runner.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,11 @@ def __init__(self) -> None:
3333
self._last_displayed_day: str | None = None
3434

3535
def fetch(self, *, exit_on_error: bool = True) -> None:
36-
"""Fetch astronomical data and display if it is new day's data."""
36+
"""Fetch astronomical data and display if not already displayed today."""
3737
lat = weather_settings.LOCATION.latitude
3838
lon = weather_settings.LOCATION.longitude
3939
logger.info(f"Using latitude={lat} longitude={lon}")
40-
4140
start = time.time()
42-
4341
try:
4442
astro = fetch_astronomical_data_from_api(lat, lon)
4543
FETCH_COUNTER.inc()
@@ -54,15 +52,16 @@ def fetch(self, *, exit_on_error: bool = True) -> None:
5452
return
5553
finally:
5654
FETCH_DURATION.observe(time.time() - start)
57-
58-
# Only display once per day
5955
today_str = date.today().isoformat()
6056
if self._last_displayed_day != today_str:
6157
display_astronomical_data(astro)
6258
self._last_displayed_day = today_str
6359

60+
def reset_display(self):
61+
"""Reset the last displayed day so display will occur again."""
62+
self._last_displayed_day = None
63+
6464
def _handle_fetch_error(self, exc: Exception, exit_on_error: bool) -> None:
65-
"""Handle errors during the fetch operation."""
6665
FETCH_ERRORS.inc()
6766
if isinstance(exc, httpx.HTTPStatusError):
6867
logger.error("HTTP status error: %s", exc)
@@ -74,10 +73,8 @@ def _handle_fetch_error(self, exc: Exception, exit_on_error: bool) -> None:
7473
logger.error("JSON decode error: %s", exc)
7574
else:
7675
logger.exception("Unexpected error")
77-
7876
if exit_on_error:
7977
raise SystemExit(1) from exc
80-
8178
raise AppError(str(exc)) from exc
8279

8380

src/sample_python_app/app/scheduler.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def start_scheduler(test_mode: bool = False) -> None:
1717
scheduler_logger = logger.bind(component="scheduler")
1818

1919
if test_mode:
20+
# Ensure display will run in tests by clearing any previous state
21+
if hasattr(fetcher, "reset_display") and callable(fetcher.reset_display):
22+
fetcher.reset_display()
2023
fetcher.fetch(exit_on_error=False)
2124
return
2225

src/sample_python_app/scripts.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""Utility scripts exposed via pyproject `project.scripts`.
2+
3+
Provides `run_checks()` which runs linters, formatters and tests.
4+
"""
5+
6+
from __future__ import annotations
7+
8+
import subprocess
9+
import sys
10+
from pathlib import Path
11+
12+
from sample_python_app.core.logging import setup_logger
13+
14+
ROOT = Path(__file__).resolve().parents[2]
15+
16+
17+
def _run(cmd: list[str]) -> None:
18+
logger = setup_logger("normal")
19+
logger.info(f"Running: {' '.join(cmd)}")
20+
subprocess.check_call(cmd, cwd=str(ROOT))
21+
22+
23+
def run_checks() -> None:
24+
"""Run ruff, isort, black and tests (coverage+pytest).
25+
26+
This is intended to be invoked via the project script entrypoint, e.g.:
27+
`uv run checks` or `python -m sample_python_app.scripts run_checks` when installed.
28+
"""
29+
py = sys.executable
30+
_run([py, "-m", "ruff", "check", ".", "--fix", "--exit-zero"])
31+
_run([py, "-m", "isort", "."])
32+
_run([py, "-m", "black", "."])
33+
_run([py, "-m", "ruff", "check", ".", "--exit-zero"])
34+
_run([py, "-m", "coverage", "run", "-m", "pytest"])
35+
36+
logger = setup_logger("normal")
37+
logger.info("All checks completed.")

tests/test_main.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import subprocess
44
import sys
55

6-
from sample_python_app.app.runner import fetch_astro_data
6+
from sample_python_app.app.runner import fetcher
77

88

99
def test_main_subprocess():
@@ -25,7 +25,10 @@ def test_main_subprocess():
2525
def test_main_runs(capfd):
2626
"""Test main run_app output."""
2727
# Call fetch_astro_data directly to test output
28-
fetch_astro_data()
28+
# Ensure display will occur during test
29+
if hasattr(fetcher, "reset_display"):
30+
fetcher.reset_display()
31+
fetcher.fetch()
2932
out, _ = capfd.readouterr()
3033
assert "Sunrise" in out
3134
assert "Sunset" in out

0 commit comments

Comments
 (0)