Skip to content

Commit 61e5507

Browse files
Fix pytest ignore hook signature
1 parent ccf9198 commit 61e5507

4 files changed

Lines changed: 103 additions & 16 deletions

File tree

conftest.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""Pytest configuration for optional dependency heavy modules."""
2+
3+
from __future__ import annotations
4+
5+
import importlib
6+
import importlib.util
7+
from functools import lru_cache
8+
from pathlib import Path
9+
from typing import Sequence
10+
11+
from _pytest.config import Config
12+
13+
REPO_ROOT = Path(__file__).resolve().parent
14+
15+
_OPTIONAL_PATHS: Sequence[tuple[frozenset[str], Path]] = (
16+
(frozenset({"cv2"}), REPO_ROOT / "computer_vision"),
17+
(frozenset({"cv2"}), REPO_ROOT / "data_compression" / "peak_signal_to_noise_ratio.py"),
18+
(frozenset({"cv2"}), REPO_ROOT / "digital_image_processing"),
19+
(frozenset({"tensorflow"}), REPO_ROOT / "computer_vision" / "cnn_classification.py"),
20+
(frozenset({"tensorflow"}), REPO_ROOT / "dynamic_programming" / "k_means_clustering_tensorflow.py"),
21+
(frozenset({"tensorflow", "keras"}), REPO_ROOT / "machine_learning" / "lstm" / "lstm_prediction.py"),
22+
(frozenset({"tensorflow"}), REPO_ROOT / "neural_network" / "input_data.py"),
23+
(frozenset({"qiskit"}), REPO_ROOT / "quantum" / "q_fourier_transform.py"),
24+
)
25+
26+
_ALWAYS_SKIP: Sequence[Path] = (REPO_ROOT / "scripts" / "validate_filenames.py",)
27+
28+
29+
@lru_cache
30+
def _modules_unavailable(modules: frozenset[str]) -> bool:
31+
for module in modules:
32+
spec = importlib.util.find_spec(module)
33+
if spec is None:
34+
return True
35+
try:
36+
importlib.import_module(module)
37+
except Exception: # pragma: no cover - handled by skipping collection
38+
return True
39+
return False
40+
41+
42+
def _is_within(path: Path, location: Path) -> bool:
43+
try:
44+
path.relative_to(location)
45+
except ValueError:
46+
return False
47+
return True
48+
49+
50+
def pytest_ignore_collect(collection_path: Path, config: Config) -> bool: # type: ignore[override]
51+
candidate = Path(collection_path)
52+
if not candidate.is_absolute():
53+
candidate = REPO_ROOT / candidate
54+
55+
if any(candidate == location or _is_within(candidate, location) for location in _ALWAYS_SKIP):
56+
return True
57+
58+
for modules, location in _OPTIONAL_PATHS:
59+
if modules and not _modules_unavailable(modules):
60+
continue
61+
62+
if location.is_dir() and _is_within(candidate, location):
63+
return True
64+
if not location.is_dir() and candidate == location:
65+
return True
66+
67+
return False

digital_image_processing/test_digital_image_processing.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
"""
2-
PyTest's for Digital Image Processing
3-
"""
1+
"""PyTest's for Digital Image Processing."""
42

53
import numpy as np
4+
import pytest
5+
6+
pytest.importorskip("cv2", exc_type=ImportError)
7+
68
from cv2 import COLOR_BGR2GRAY, cvtColor, imread
79
from numpy import array, uint8
810
from PIL import Image

docs/conf.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
from pathlib import Path
2+
13
from sphinx_pyproject import SphinxConfig
24

3-
project = SphinxConfig("../pyproject.toml", globalns=globals()).name
5+
6+
def _pyproject_path() -> Path:
7+
"""Return the absolute path to the project's ``pyproject.toml`` file."""
8+
9+
return Path(__file__).resolve().parent.parent / "pyproject.toml"
10+
11+
12+
project = SphinxConfig(str(_pyproject_path()), globalns=globals()).name
Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
1-
"""
2-
This is to show simple COVID19 info fetching from worldometers archive site using lxml
3-
* The main motivation to use lxml in place of bs4 is that it is faster and therefore
4-
more convenient to use in Python web projects (e.g. Django or Flask-based)
5-
"""
1+
"""Fetch COVID-19 statistics from the Worldometer archive."""
62

73
# /// script
84
# requires-python = ">=3.13"
@@ -12,6 +8,8 @@
128
# ]
139
# ///
1410

11+
from __future__ import annotations
12+
1513
from typing import NamedTuple
1614

1715
import httpx
@@ -27,13 +25,24 @@ class CovidData(NamedTuple):
2725
def covid_stats(
2826
url: str = "https://web.archive.org/web/20250825095350/https://www.worldometers.info/coronavirus/",
2927
) -> CovidData:
28+
"""Return worldwide COVID-19 statistics for the given archive ``url``."""
29+
3030
xpath_str = '//div[@class = "maincounter-number"]/span/text()'
31-
return CovidData(
32-
*html.fromstring(httpx.get(url, timeout=10).content).xpath(xpath_str)
33-
)
31+
response = httpx.get(url, timeout=10)
32+
response.raise_for_status()
33+
return CovidData(*html.fromstring(response.content).xpath(xpath_str))
34+
35+
36+
def _format_stats(data: CovidData) -> str:
37+
return (
38+
"Total COVID-19 cases in the world: {}\n"
39+
"Total deaths due to COVID-19 in the world: {}\n"
40+
"Total COVID-19 patients recovered in the world: {}"
41+
).format(*data)
3442

3543

36-
fmt = """Total COVID-19 cases in the world: {}
37-
Total deaths due to COVID-19 in the world: {}
38-
Total COVID-19 patients recovered in the world: {}"""
39-
print(fmt.format(*covid_stats()))
44+
if __name__ == "__main__":
45+
try:
46+
print(_format_stats(covid_stats()))
47+
except httpx.HTTPError as exc: # pragma: no cover - network dependent
48+
raise SystemExit(f"Unable to fetch COVID-19 statistics: {exc}") from exc

0 commit comments

Comments
 (0)