1.4.0 Many bugfixes#27
Merged
Merged
Conversation
Framework adapters: - Flask: record calls with query strings (previously dropped); record the actual matched rule for multi-decorated views (return_rule=True); record followed trailing-slash redirects; exclude static routes by endpoint name (covers custom static_url_path and blueprint statics) - FastAPI: discover plain Starlette routes and mounted Starlette sub-apps (docs routes stay excluded); record post-redirect path so slash-redirected calls count against the real route - Django: convert re_path regexes to matchable <name> templates (previously never matched); count only implemented handler methods for CBVs (was inflating every view to 6 methods incl. TRACE) - Detection: isinstance-based for Flask/FastAPI so app subclasses work; Django fallback no longer substring-matches unrelated modules Plugin / xdist: - Register DeferXdistPlugin only when --api-cov-report is active (was registered on every xdist run and crashed on any worker crash) - Tolerate crashed workers with no workeroutput - Merge discovered endpoints from all workers (was first-worker-wins) - CoverageWrapper: delegate context-manager protocol and repr (with-blocks on wrapped clients no longer TypeError) Report / config: - Path parameters match a single segment ([^/]+) so nested-path calls no longer mark parent routes covered; path converters still match greedily - fail_under now fails when discovery finds nothing, and is vacuous (not a spurious failure) when every endpoint is excluded - Implement the documented group-methods-by-endpoint option (was a no-op) - --api-cov-fail-under=0 no longer discarded as falsy - parse_openapi_spec tolerates empty/non-mapping specs instead of crashing fixture setup; cli show-conftest no longer mangles paths containing .py Packaging / CI / docs: - pytest>=7.0 (pytest.Parser/Config are imported at collection time and do not exist before 7.0); add classifiers, keywords, py.typed - publish workflow: tag only after successful publish, run non-mutating make pipeline-ci (auto-fixing lint could publish code matching no commit), pass token via UV_PUBLISH_TOKEN instead of a temp file - CI: re-enable format/lint as non-mutating checks - README: JSON example uses real method-prefixed keys; PUBLISH.md matches the actual Makefile targets and workflow order
Each fix was validated with a dedicated repro check run before (failing) and after (passing) the change; all ten now pass. Django route templates (frameworks.py): - Regex shorthand (\d, \w), bare character classes and bare dots outside groups become placeholders instead of garbage literals; quantifiers are consumed; a trailing '/?' is dropped - Groups whose body can span '/' (bare dot, '/', \S) get a <path:...> converter so multi-segment values like (?P<rest>.*) still match after the single-segment matching change - Dispatch-only CBVs (no verb handlers) keep the default method set instead of vanishing from discovery Recording and discovery: - FastAPI: the requested path is always credited (a redirecting route keeps its own coverage) and a followed redirect also credits the final route - Flask: static-rule exclusion additionally requires the rule to end in /<path:filename>, so user routes named '*.static' are kept - Django detection: isinstance check against BaseHandler so handler subclasses in user modules are detected; optional-framework classes are resolved via sys.modules with failed imports memoised (no repeated finder scans, and robust to pytester's sys.modules snapshotting) Gate semantics (report.py): - All-endpoints-excluded now fails closed when fail_under > 0 (an over-broad exclusion pattern can no longer silently disable the gate) and passes when fail_under == 0 - Empty discovery only hard-fails for fail_under > 0; an explicit 0 is trivially satisfied - Method-scoped exclusion patterns are applied before method grouping so they keep working under --api-cov-group-methods-by-endpoint Tests: 12 new regression tests covering every finding; updated the two tests whose expectations changed (all-excluded gate, redirect recording).
plugin.py:
- Extract _tracked_client_flow: create_coverage_fixture and coverage_client
shared ~45 duplicated lines of client-resolution flow that had already
drifted; both now delegate to one generator (coverage_client additionally
gains create_coverage_fixture's catch around tracked-client construction)
- Share DEFAULT_CLIENT_FIXTURE_NAMES with config.py instead of a second
hardcoded tuple; collapse the doubled --api-cov-report getoption in
pytest_configure; 'except (FixtureLookupError, Exception)' -> Exception;
bare partition() replaces three 'if "?" in url' conditionals; xdist
endpoint merge uses dict.fromkeys for ordered dedup
- Parse the OpenAPI spec once per session: a spec yielding zero endpoints
was re-read and re-parsed on every test (new
SessionData.openapi_discovery_attempted flag)
report.py:
- Split categorise_endpoints into _partition_excluded + _match_covered
(categorise_endpoints stays as their composition), collapsing the
quadruplicated exclusion/negation match loops into one _matches_any
helper and turning generate's grouping branch into a linear pipeline
with no sentinel arguments
- _split_endpoint helper replaces four inline METHOD/path split idioms;
group_endpoints_by_path uses dict.fromkeys; _compile_exclusion_patterns
returns tuples instead of Optionals; footer now derives from the header
string (was 2 chars wider via magic arithmetic); endpoint_to_regex cache
unbounded so >512-endpoint sessions stop thrashing
frameworks.py:
- Merge the APIRoute and plain-Route discovery branches; _SKIPPED_METHODS
constant replaces four ('HEAD', 'OPTIONS') literals
- Django template scanner: quantifier matching via one compiled regex,
_skip_class helper replaces duplicated character-class handling, and an
itertools.count param namer removes the param_count tuple-threading
- Bind the recorder once after the None guard in all three tracking
clients, deleting dead 'if recorder is not None' checks and a type-ignore
- Inline single-caller _detect_by_isinstance into _detect_framework; drop
the duplicate None check in is_supported_framework
openapi.py: logger.exception implies exc_info; drop the redundant argument.
Replaces the hand-rolled character scanner in _django_route_to_template
(and its _consume_quantifier/_skip_class/_group_placeholder helpers) with
a walk of the stdlib regex parse tree (re._parser on 3.11+, sre_parse on
3.10). Every rule now reads off a node type instead of character
arithmetic, and multi-segment detection inspects what the pattern can
actually match rather than grepping its source text.
Behavioral deltas, all covered by new tests:
- (?P<a>[^/]+) is now correctly single-segment (the old text heuristic saw
the '/' inside the negated class and emitted <path:a>)
- top-level alternation (legacy|new) degrades to a matchable placeholder
instead of passing through as an unmatchable literal
- bounded repeats (a{2,4}) become placeholders instead of leaking regex
syntax into the template
- unparseable input passes through verbatim, so a future change to the
private parser API degrades to unconverted routes rather than crashing
(discovery already guards adapter failures)
Known trade-off, accepted deliberately: re._parser is a private stdlib
API. It has been stable for two decades, a large ecosystem (hypothesis,
sre_yield, exrex) walks the same tree, CI covers 3.10-3.14, and the
verbatim fallback bounds the failure mode. Verified on 3.12 and on a
fresh 3.10 environment (sre_parse branch).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Many bugfixes and refactors