2026-01-02 14:34:31 -06:00
|
|
|
import os
|
2026-03-07 17:05:41 -06:00
|
|
|
import time
|
|
|
|
|
from functools import wraps
|
|
|
|
|
|
2026-01-02 14:34:31 -06:00
|
|
|
import requests
|
|
|
|
|
|
|
|
|
|
|
2026-03-07 17:05:41 -06:00
|
|
|
def _get_env_number(name, default, cast):
|
|
|
|
|
value = os.environ.get(name)
|
|
|
|
|
if value is None:
|
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
return cast(value)
|
|
|
|
|
except ValueError:
|
|
|
|
|
return default
|
|
|
|
|
|
|
|
|
|
|
2026-01-02 14:34:31 -06:00
|
|
|
def pytest_configure(config):
|
|
|
|
|
if not getattr(config.option, "check_links", False):
|
|
|
|
|
return
|
|
|
|
|
|
2026-03-07 17:05:41 -06:00
|
|
|
timeout = _get_env_number("CHECK_LINKS_TIMEOUT", 10.0, float)
|
|
|
|
|
max_retries = max(0, _get_env_number("CHECK_LINKS_RETRIES", 2, int))
|
|
|
|
|
retry_backoff = max(0.0, _get_env_number("CHECK_LINKS_RETRY_BACKOFF", 1.0, float))
|
|
|
|
|
current_request = requests.sessions.Session.request
|
|
|
|
|
|
|
|
|
|
if getattr(current_request, "_check_links_wrapped", False):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
retryable_methods = {"GET", "HEAD"}
|
|
|
|
|
retryable_errors = (
|
|
|
|
|
requests.exceptions.Timeout,
|
|
|
|
|
requests.exceptions.ConnectionError,
|
|
|
|
|
)
|
2026-01-02 14:34:31 -06:00
|
|
|
|
2026-03-07 17:05:41 -06:00
|
|
|
@wraps(current_request)
|
2026-01-02 14:34:31 -06:00
|
|
|
def request_with_timeout(self, method, url, **kwargs):
|
|
|
|
|
if kwargs.get("timeout") is None:
|
|
|
|
|
kwargs["timeout"] = timeout
|
|
|
|
|
|
2026-03-07 17:05:41 -06:00
|
|
|
method_name = (method or "").upper()
|
|
|
|
|
|
|
|
|
|
for attempt in range(max_retries + 1):
|
|
|
|
|
try:
|
|
|
|
|
return current_request(self, method, url, **kwargs)
|
|
|
|
|
except retryable_errors:
|
|
|
|
|
should_retry = method_name in retryable_methods and attempt < max_retries
|
|
|
|
|
if not should_retry:
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
# Retries smooth over transient CI/network blips without masking real 4xx/5xx failures.
|
|
|
|
|
if retry_backoff:
|
|
|
|
|
time.sleep(retry_backoff * (attempt + 1))
|
|
|
|
|
|
|
|
|
|
request_with_timeout._check_links_wrapped = True
|
2026-01-02 14:34:31 -06:00
|
|
|
requests.sessions.Session.request = request_with_timeout
|