From ac9dcfdfb3f05649a3f6b36813377ce804f9a0ad Mon Sep 17 00:00:00 2001 From: Sinan Ozel <4183117+sinan-ozel@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:50:38 -0400 Subject: [PATCH] Add --openapi-retries and --openapi-retry-wait CLI options Both validate_openapi_spec and the plugin's own /openapi.json fetch now wait retry_wait seconds between attempts instead of retrying immediately. The number of retries and the wait duration are user-configurable: --openapi-retries N retries after first failure (default: 3) --openapi-retry-wait SECS seconds between retries (default: 1.0) Co-Authored-By: Claude Sonnet 4.6 --- src/pytest_openapi/openapi.py | 15 +++++++---- src/pytest_openapi/plugin.py | 47 +++++++++++++++++++++++++++++------ 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/pytest_openapi/openapi.py b/src/pytest_openapi/openapi.py index 3d35d35..f40c5dd 100644 --- a/src/pytest_openapi/openapi.py +++ b/src/pytest_openapi/openapi.py @@ -1,6 +1,7 @@ """OpenAPI specification validation and parsing.""" import sys +import time import requests @@ -178,7 +179,7 @@ def check_endpoint_schema_descriptions(method, path, operation): return errors -def validate_openapi_spec(base_url, timeout=10): +def validate_openapi_spec(base_url, timeout=10, retries=3, retry_wait=1.0): """Validate that the OpenAPI spec is available and meets requirements. @@ -190,14 +191,17 @@ def validate_openapi_spec(base_url, timeout=10): Args: base_url: Base URL of the API server + timeout: HTTP request timeout in seconds + retries: Number of retry attempts after the first failure (default: 3) + retry_wait: Seconds to wait between retries (default: 1.0) Raises: SystemExit: If validation fails """ openapi_url = f"{base_url}/openapi.json" + max_attempts = retries + 1 - # Check 1: Fetch OpenAPI spec (with up to 3 retries) - max_attempts = 4 + # Check 1: Fetch OpenAPI spec last_error = None spec = None for attempt in range(1, max_attempts + 1): @@ -211,8 +215,10 @@ def validate_openapi_spec(base_url, timeout=10): if attempt < max_attempts: print( f"\n⚠️ Attempt {attempt}/{max_attempts} failed" - f" fetching {openapi_url}: {e}. Retrying..." + f" fetching {openapi_url}: {e}." + f" Retrying in {retry_wait}s..." ) + time.sleep(retry_wait) except ValueError as e: last_error = e break @@ -228,7 +234,6 @@ def validate_openapi_spec(base_url, timeout=10): ) print(f" Reason: {last_error}") sys.exit(1) - # TODO: Add JsonDecodeError handling # Validate the spec structure if "paths" not in spec: diff --git a/src/pytest_openapi/plugin.py b/src/pytest_openapi/plugin.py index 52b3ec6..1ca8d01 100644 --- a/src/pytest_openapi/plugin.py +++ b/src/pytest_openapi/plugin.py @@ -2,6 +2,7 @@ import json import re +import time import pytest import requests @@ -64,6 +65,26 @@ def pytest_addoption(parser): " this will be ignored" ), ) + group.addoption( + "--openapi-retries", + action="store", + metavar="N", + default=3, + help=( + "Number of times to retry fetching /openapi.json on failure" + " (default: 3)" + ), + ) + group.addoption( + "--openapi-retry-wait", + action="store", + metavar="SECONDS", + default=1.0, + help=( + "Seconds to wait between /openapi.json fetch retries" + " (default: 1.0)" + ), + ) def pytest_configure(config): @@ -84,14 +105,22 @@ def pytest_configure(config): no_stdout = config.getoption("--openapi-no-stdout") ignore_pattern = config.getoption("--openapi-ignore") openapi_timeout = float(config.getoption("--openapi-timeout")) + openapi_retries = int(config.getoption("--openapi-retries")) + openapi_retry_wait = float(config.getoption("--openapi-retry-wait")) + max_attempts = openapi_retries + 1 # Run validation checks - validate_openapi_spec(base_url, timeout=openapi_timeout) + validate_openapi_spec( + base_url, + timeout=openapi_timeout, + retries=openapi_retries, + retry_wait=openapi_retry_wait, + ) - # Fetch the OpenAPI spec (with up to 3 retries) + # Fetch the OpenAPI spec spec = None last_exc = None - for attempt in range(1, 5): + for attempt in range(1, max_attempts + 1): try: response = requests.get( f"{base_url}/openapi.json", timeout=openapi_timeout @@ -101,15 +130,17 @@ def pytest_configure(config): break except (requests.exceptions.RequestException, ValueError) as e: last_exc = e - if attempt < 4: + if attempt < max_attempts: print( - f"\n⚠️ Attempt {attempt}/4 failed fetching" - f" OpenAPI spec: {e}. Retrying..." + f"\n⚠️ Attempt {attempt}/{max_attempts} failed fetching" + f" OpenAPI spec: {e}. Retrying in" + f" {openapi_retry_wait}s..." ) + time.sleep(openapi_retry_wait) if spec is None: pytest.exit( - f"\n❌ Failed to fetch OpenAPI spec after 4 attempts:" - f" {last_exc}", + f"\n❌ Failed to fetch OpenAPI spec after {max_attempts}" + f" attempts: {last_exc}", returncode=1, )