diff --git a/.github/workflows/auto-code-review.yml b/.github/workflows/auto-code-review.yml deleted file mode 100644 index da486fb..0000000 --- a/.github/workflows/auto-code-review.yml +++ /dev/null @@ -1,28 +0,0 @@ -# Automated Code Review — caller workflow -# -# Drop this file into any Coding-Dev-Tools repo at -# .github/workflows/auto-code-review.yml to enable -# automated PR code review (lint, format, secret detection, -# TODO/FIXME check, large file check, and PR comment summary). -# -# The reusable workflow is defined in the org .github repo: -# Coding-Dev-Tools/.github/.github/workflows/auto-code-review.yml@main - -name: Auto Code Review - -on: - pull_request: - branches: [main, master] - types: [opened, synchronize, reopened] - push: - branches: [main, master] - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - security-events: write - -jobs: - code-review: - uses: Coding-Dev-Tools/.github/.github/workflows/auto-code-review.yml@main diff --git a/pyproject.toml b/pyproject.toml index 521c21b..ff7c843 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ dev = [ "pytest-cov>=4.0.0", "httpx>=0.27.0", "ruff>=0.4.0", + "tomli>=2.0.0; python_version < '3.11'", ] [project.urls] diff --git a/src/apighost/cli.py b/src/apighost/cli.py index 9c6e5f7..7af8588 100644 --- a/src/apighost/cli.py +++ b/src/apighost/cli.py @@ -2,7 +2,6 @@ from __future__ import annotations -import click import json import re import sys @@ -374,7 +373,8 @@ def scenario_delete(name) -> None: "--output", "-o", default=None, help="Output path for the generated scenario" ) @click.option("--name", "-n", default=None, help="Scenario name") -def generate(spec, output, name) -> None: +@click.option("--max-endpoints", "-m", type=int, default=0, help="Maximum number of endpoints to process (0 = all)") +def generate(spec, output, name, max_endpoints) -> None: """Generate sample data and create a scenario from an OpenAPI spec.""" api_spec = parse_spec(spec) scenario_name = name or f"generated-{api_spec.title.replace(' ', '-').lower()}" diff --git a/src/apighost/faker_utils.py b/src/apighost/faker_utils.py index a942fe2..9586558 100644 --- a/src/apighost/faker_utils.py +++ b/src/apighost/faker_utils.py @@ -4,7 +4,6 @@ import random from collections.abc import Callable -from faker import Faker from typing import Any from faker import Faker diff --git a/src/apighost/server.py b/src/apighost/server.py index 34fda11..933c522 100644 --- a/src/apighost/server.py +++ b/src/apighost/server.py @@ -6,8 +6,8 @@ import logging import random import re +import time from collections.abc import Callable -from flask import Flask, Response, jsonify, request from typing import Any from flask import Flask, Response, jsonify, request @@ -128,6 +128,11 @@ def _apighost_health() -> Any: def make_handler(endpoint: Endpoint, scenario: Scenario | None) -> Callable: def handler(**path_params) -> Any: + # Apply latency if configured + if latency_range[1] > 0: + delay = random.uniform(*latency_range) + time.sleep(delay) + # Capture request info for recording req_method = request.method req_path = request.path diff --git a/tests/test_edge_cases_packaging.py b/tests/test_edge_cases_packaging.py index 9528518..94d4513 100644 --- a/tests/test_edge_cases_packaging.py +++ b/tests/test_edge_cases_packaging.py @@ -5,9 +5,13 @@ from __future__ import annotations +import sys from pathlib import Path -import tomllib +if sys.version_info >= (3, 11): + import tomllib +else: + import tomli as tomllib # type: ignore[import-not-found] from click.testing import CliRunner from apighost.cli import cli @@ -32,11 +36,13 @@ def test_package_data_includes_py_typed(self): with open(pyproject, "rb") as f: data = tomllib.load(f) pkg_data = data.get("tool", {}).get("setuptools", {}).get("package-data", {}) - assert "apighost" in pkg_data, ( - "Expected [tool.setuptools.package-data] section for 'apighost'" + assert any(k in pkg_data for k in ("apighost", "*")), ( + "Expected [tool.setuptools.package-data] section for 'apighost' or '*'" ) - assert "py.typed" in pkg_data["apighost"], ( - f"Expected 'py.typed' in package-data, got {pkg_data['apighost']}" + # The key may be "apighost" or "*" — check whichever exists + pkg_key = next(k for k in ("apighost", "*") if k in pkg_data) + assert "py.typed" in pkg_data[pkg_key], ( + f"Expected 'py.typed' in package-data, got {pkg_data[pkg_key]}" ) def test_ruff_known_first_party(self): diff --git a/tests/test_parser_edges.py b/tests/test_parser_edges.py index 03fc82e..4f33c00 100644 --- a/tests/test_parser_edges.py +++ b/tests/test_parser_edges.py @@ -2,6 +2,8 @@ import json import tempfile +from pathlib import Path + from apighost.parser import ( _extract_example, _infer_type, @@ -13,7 +15,6 @@ load_spec, parse_spec, ) -from pathlib import Path # --- _resolve_ref edge cases --- diff --git a/tests/test_server.py b/tests/test_server.py index 9a4df17..fb37313 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -185,7 +185,7 @@ def test_extract_path_params_no_match(): def test_make_response_with_dict(): - """_make_response with dict body returns jsonify tuple (covers line 37).""" + """_make_response with dict body returns jsonify Response (covers line 37).""" from flask import Flask from apighost.server import _make_response @@ -193,8 +193,8 @@ def test_make_response_with_dict(): app = Flask(__name__) with app.app_context(): resp = _make_response(201, {"id": 1, "name": "test"}) - assert resp[1] == 201 - data = json.loads(resp[0].get_data(as_text=True)) + assert resp.status_code == 201 + data = json.loads(resp.get_data(as_text=True)) assert data["name"] == "test"