Python API
CoverageMap
CoverageMap
Tracks API test coverage against an OpenAPI specification.
Note
Examples on this page that use coverage without creating it assume a map built with
coverage = tracecov.CoverageMap.from_path("openapi.json").
from_dict
from_dict(schema, *, base_path=None, location=None)
Create a coverage map from a parsed OpenAPI specification.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
schema |
dict |
Parsed OpenAPI specification | required |
base_path |
str | None |
Base path prefix for all operations. When omitted, taken from the path of servers[0].url (OpenAPI 3) or basePath (Swagger 2.0) |
None |
location |
str | None |
File path or URL of the specification, shown in error messages. $refs to other files resolve against its directory, or against the working directory when it is None; a URL reads no other files |
None |
Example
import tracecov
# From a dictionary, for example loaded from a file or an API response
schema = {"openapi": "3.0.0", "info": {"title": "API", "version": "1.0"}, "paths": {}}
coverage = tracecov.CoverageMap.from_dict(schema)
# With base path for APIs hosted under a prefix
coverage = tracecov.CoverageMap.from_dict(schema, base_path="/api/v1")
Tip
To load a schema from a URL, use from_url() instead.
from_path
from_path(path, *, base_path=None)
Create a coverage map from an OpenAPI specification file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path |
str | Path |
Path to OpenAPI specification (JSON or YAML) | required |
base_path |
str | None |
Base path prefix for all operations. When omitted, taken from the path of servers[0].url (OpenAPI 3) or basePath (Swagger 2.0) |
None |
Example
import tracecov
coverage = tracecov.CoverageMap.from_path("openapi.json")
coverage = tracecov.CoverageMap.from_path("openapi.yaml", base_path="/api/v1")
from_url
from_url(url, *, base_path=None)
Create a coverage map from an OpenAPI specification URL.
Fetches the schema from the given URL and parses it as JSON or YAML.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to fetch the OpenAPI specification from | required |
base_path |
str | None |
Base path prefix for all operations. When omitted, taken from the path of servers[0].url (OpenAPI 3) or basePath (Swagger 2.0) |
None |
Example
import tracecov
coverage = tracecov.CoverageMap.from_url("https://api.example.com/openapi.json")
coverage = tracecov.CoverageMap.from_url(
"https://api.example.com/openapi.yaml",
base_path="/api/v1"
)
record
record(request, response=None, timestamp=None, labels=None)
Record an HTTP interaction for coverage tracking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request |
HttpRequest |
Request to record | required |
response |
HttpResponse | None |
Response to record | None |
timestamp |
float | None |
Unix timestamp of the interaction | None |
labels |
Iterable[str] | None |
Coverage labels for this interaction, added to those of the enclosing labeled() blocks. Professional only |
None |
Example
from tracecov import HttpRequest, HttpResponse
coverage.record(
request=HttpRequest(
method="GET",
url="https://api.example.com/users/1",
headers={"Authorization": "Bearer token"},
),
response=HttpResponse(
status_code=200,
elapsed=0.5,
body=b'{"id": 1, "name": "Alice"}',
headers={"Content-Type": "application/json"},
),
)
record_error
record_error(method, path, message)
Attach an error message to an operation. It appears in the operation's errors list in reports.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method |
str |
HTTP method | required |
path |
str |
Path template as written in the specification | required |
message |
str |
Error message | required |
generate_report
generate_report(*, format="html", title=None)
Generate a coverage report.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
format |
str |
Output format: "html", "json", or "markdown" |
"html" |
title |
str | None |
Custom title for HTML reports | None |
Returns: Report content as a string. See JSON Report Format for JSON schema and usage.
Every percent in the JSON report is null when that dimension's total is 0.
Example
import json
# HTML report
html = coverage.generate_report(title="My API Coverage")
# JSON report for CI integration
data = json.loads(coverage.generate_report(format="json"))
percent = data["summary"]["operations"]["percent"]
if percent is not None and percent < 80:
raise SystemExit("Coverage below threshold")
save_report
save_report(*, output_file=None, format="html", title=None)
Save a coverage report to a file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
output_file |
str | None |
Output path (coverage.html, coverage.json, or coverage.md by default) |
None |
format |
str |
Output format: "html", "json", or "markdown" |
"html" |
title |
str | None |
Custom title for HTML reports | None |
Example
coverage.save_report() # saves to coverage.html
coverage.save_report(output_file="report.html", title="API Coverage")
coverage.save_report(format="json") # saves to coverage.json
generate_html_report / generate_json_report
generate_html_report(*, title=None)
generate_json_report()
Same as generate_report(format="html", title=...) and generate_report(format="json").
Returns: Report content as a string.
generate_markdown_report
generate_markdown_report(*, report_url=None, weak_threshold=None, is_pull_request=False, sub_text=None, columns=None)
Generate a Markdown report for GitHub step summaries and pull request comments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
report_url |
str | None |
Link to the full HTML report, shown in the footer | None |
weak_threshold |
float | None |
Parameter coverage ratio (0.0–1.0) below which an operation is flagged | 1.0 |
is_pull_request |
bool |
Word the headline as the effect of merging a pull request | False |
sub_text |
str | None |
Text shown in <sub> under the footer |
None |
columns |
list[str] | None |
Columns to show: "parameters", "keywords", "responses", "examples". Must not be empty |
all |
Returns: Markdown report as a string.
Example
markdown = coverage.generate_markdown_report(
report_url="https://ci.example.com/coverage.html",
is_pull_request=True,
columns=["parameters", "responses"],
)
save_html_report / save_json_report / save_markdown_report
save_html_report(*, output_file=None, title=None)
save_json_report(*, output_file=None)
save_markdown_report(*, output_file=None, report_url=None, weak_threshold=None, is_pull_request=False, sub_text=None, columns=None)
Write the corresponding report to output_file: coverage.html, coverage.json, or coverage.md by default.
The remaining parameters match generate_html_report() and
generate_markdown_report().
build_json_report
build_json_report()
The JSON report as a dict, without serializing it to a string.
Returns: dict
operation_report / operation_reports
operation_report(method, path)
operation_reports()
Per-operation entries of the JSON report. path is the path as it appears in the report,
including the base path. operation_report() returns None when no such operation exists.
Returns: dict | None and list[dict].
Example
report = coverage.operation_report("GET", "/users/{id}")
if report is not None:
print(report["coverage"]["parameters"])
generate_text_report
generate_text_report(*, width, colored=True, skip_covered=False, skip_empty=False, show_missing=None)
Generate a text coverage report for terminal output.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width |
int |
Terminal width for formatting | required |
colored |
bool |
Use ANSI colors in output | True |
skip_covered |
bool |
Hide fully covered items | False |
skip_empty |
bool |
Hide items with no traffic | False |
show_missing |
list[str] | None |
Sections listing uncovered items. The only accepted value is "parameter" |
None |
Returns: Formatted text report as a string.
Example
print(coverage.generate_text_report(width=80, colored=False, show_missing=["parameter"]))
# Compact view without fully covered items
report = coverage.generate_text_report(width=80, skip_covered=True)
Output after recording a single GET /users/1:
Operation Params Missed Partial Cover
--------------------------------------------------------------------------------
GET /users 1 1 0 0.00%
POST /users 1 1 0 0.00%
GET /users/{id} 1 0 1 0.00%
--------------------------------------------------------------------------------
TOTAL 3 2 1 0.00%
Uncovered Parameters:
GET /users:
query: limit
POST /users:
body: application/json
statistic
statistic()
Get aggregate coverage statistics.
Returns: Dictionary with keys:
operations-{"seen": int, "total": int, "timeline": list}parameters-{"positive": int, "negative": int, "partial": int, "full": int, "total": int, "timeline": list}keywords-{"positive": int, "negative": int, "partial": int, "full": int, "total": int, "timeline": list}examples-{"seen": int, "total": int, "timeline": list}responses-{"seen": int, "total": int, "elapsed_total_seconds": float, "timeline": list}response_keywords-{"reached": int, "violated": int, "total": int}
Example
stats = coverage.statistic()
print(f"Operations: {stats['operations']['seen']}/{stats['operations']['total']}")
print(f"Keywords: {stats['keywords']['full']}/{stats['keywords']['total']}")
check_thresholds
check_thresholds(*, operations=None, parameters=None, keywords=None, examples=None, responses=None, response_keywords=None, label=None)
Compare coverage against minimum percentages (0–100). Unset dimensions and dimensions with a total of 0 are skipped. With label, compare that label's coverage instead of the totals.
Raises: ValueError when label names a label with no recorded traffic.
Returns: List of violations, empty when every threshold is met. Each is a dictionary with keys:
dimension-"operations","parameters","keywords","examples","responses", or"response_keywords"actual- Measured percentageminimum- Required percentage
Example
violations = coverage.check_thresholds(operations=80, parameters=80)
for v in violations:
print(f"{v['dimension']}: {v['actual']:.1f}% < {v['minimum']:.0f}%")
coverage_gaps
coverage_gaps()
Get everything the tests have not covered yet, as a flat list.
Returns: List of dictionaries. Each has method, path, and kind; the other keys depend on kind:
kind |
Extra keys |
|---|---|
"operation_unseen" |
- |
"response_uncovered" |
status_code |
"parameter_uncovered" |
location, parameter |
"keyword_miss", "keyword_needs_valid", "keyword_needs_invalid" |
location, parameter, schema_path, satisfiability |
"example_unseen" |
location, parameter |
location is one of "path", "query", "querystring", "header", "cookie", "body", "form_data".
satisfiability is "Normal", "Positive", or "Negative".
Example
for gap in coverage.coverage_gaps():
if gap["kind"] == "keyword_needs_invalid":
print(f"{gap['method']} {gap['path']} {gap['parameter']}{gap['schema_path']}: needs an invalid value")
uncovered_keywords
uncovered_keywords()
Deprecated
Use coverage_gaps() instead.
Get a list of keywords without full coverage.
Returns: List of dictionaries with keys:
method- HTTP methodpath- API pathlocation- Parameter location (path,query,querystring,header,cookie,body,form_data)parameter- Parameter nameschema_path- JSON pointer to the keyword in the schemastate- What coverage is missing:"miss"(no values at all),"needs_invalid", or"needs_valid"satisfiability- Satisfiability constraint ("Normal","Positive", or"Negative")
suggestions
suggestions()
Requests that would cover the keywords your tests missed, one entry per operation and parameter location.
Professional only; Community raises ValueError.
Returns: List of dictionaries with keys:
method,path- The operationlocation-"path","query","header","cookie","form_data", or"body"media_type- Body onlygaps- Uncovered keywords, each withparameter,pointer(the keyword within the parameter's schema) andpolarity("valid"or"invalid")requests- Suggested requests, each withpolarity,value(parameters by name, or the body itself) andcovers(indexes intogaps)hidden- Suggested requests left out ofrequests, which keeps the widest onesuncovered- Gaps no request covers, each withgap(an index intogaps) andreason:"unsupported"(not targeted yet),"unsatisfiable"(no value reaches it),"exhausted"(no attempt covered it) or"heavy"(the time budget ran out first)
Example
After a single GET /items?limit=10 against limit: {"type": "integer", "minimum": 1, "maximum": 100}:
for entry in coverage.suggestions():
for request in entry["requests"]:
print(entry["method"], entry["path"], request["polarity"], request["value"])
GET /items invalid {'limit': 101}
GET /items invalid {'limit': 0}
GET /items invalid {'limit': None}
diagnostics
diagnostics()
Problems found in the specification that affect coverage, such as invalid patterns or unresolvable references.
Returns: List of dictionaries with severity ("error" or "warning"), kind, at (where the
problem is), and the fields that kind carries. See Diagnostics for the kinds.
Example
for diagnostic in coverage.diagnostics():
print(diagnostic["severity"], diagnostic["kind"], diagnostic["at"])
A query parameter whose pattern is "[a-" produces:
{
"severity": "warning",
"kind": "invalid_schema",
"schema_path": "/pattern",
"message": '`pattern` must be a valid regex; found `"[a-"`',
"at": {"path": "/users", "in": "parameter", "method": "GET", "location": "query", "name": "q"},
}
unmatched_requests
unmatched_requests()
Get the requests that matched no documented operation, most frequent first. Grouped by
(method, host, path). Reporting only - never affects coverage percentages or thresholds.
Returns: List of dictionaries with keys:
method- HTTP methodhost- Request authority, including the port when presentpath- Path template formethod_not_documented, observed request path otherwisereason-"no_matching_path"or"method_not_documented"count- Requests in this groupfirst_seen_at- Timestamp of the first request recorded into the group
At most 1000 distinct groups are tracked; see unmatched_overflow().
Example
for entry in coverage.unmatched_requests():
print(f"{entry['method']} {entry['host']}{entry['path']}: {entry['reason']} (x{entry['count']})")
unmatched_overflow
unmatched_overflow()
Requests dropped after the distinct-group cap was reached. A non-zero value means
unmatched_requests() is truncated.
Returns: int
dropped_requests
dropped_requests()
Requests discarded before matching could be attempted - an unparsable URL (an unresolved
{{baseUrl}} in a Postman collection, say) or an HTTP method TraceCov does not model.
A non-zero value with empty unmatched_requests() explains zero coverage.
Returns: int
export_counts
export_counts()
Everything this map recorded, as opaque bytes for merge_counts() on a map of the same
specification in another process. The pytest plugin uses it to collect coverage from
pytest-xdist workers.
Returns: bytes
merge_counts
merge_counts(counts)
Add counts that export_counts() returned for a map of the same specification, as if this
map had recorded their traffic too.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
counts |
bytes |
What export_counts() returned |
required |
Raises: ValueError when counts is malformed or does not fit this map's specification.
Counts from another specification of the same shape are not detected.
covered_by
covered_by(method, path, *, location=None, name=None, keyword=None, status=None, media_type=None)
Get the coverage labels that reached an element, in name order. Professional only.
- Operation:
methodandpathalone. - Parameter:
location("path","query","querystring","header","cookie","body"or"form_data") andname, which is the media type for"body"; addkeyword, a JSON pointer into the parameter schema, for one keyword. - Response:
status; addkeywordfor a response body keyword, andmedia_typewhen the response declares more than one.
Returns: List of dictionaries with keys:
label- Label namereached-"valid","invalid"or"both": whether the label sent values that satisfy the element, violate it, or both
Raises: ValueError when no such element exists in the specification.
Example
labels = coverage.covered_by(
"GET",
"/test",
location="query",
name="id",
keyword="/minLength",
)
for entry in labels:
print(entry["label"], entry["reached"])
label_summary / label_summaries
label_summary(label) / label_summaries()
Coverage of one label, in the shape of statistic(), or None when the label has no recorded traffic. label_summaries() maps every label to its summary. Professional only.
Returns: LabelSummary | None / dict[str, LabelSummary]
label_overflow
label_overflow()
New labels dropped after the map reached 100,000 distinct labels, counted once per request that carried one. A non-zero value means some label summaries are missing.
Returns: int
requests
requests
Integration for the requests library.
Returns: RequestsPlugin instance.
httpx
httpx
Integration for the httpx library.
Returns: HttpxPlugin instance.
postman
postman
Import traffic from Postman Collections (Professional).
Returns: PostmanPlugin instance.
har
har
Import traffic from HAR files (Professional).
Returns: HarPlugin instance.
vcr
vcr
Import traffic from VCR cassettes (Professional).
Returns: VcrPlugin instance.
django
django
Integration for Django test clients.
Returns: DjangoPlugin instance.
flask
flask
Integration for Flask test clients.
Returns: FlaskPlugin instance.
RequestsPlugin
RequestsPlugin
Integration for the requests library.
track_session
track_session(session)
Attach coverage tracking to a requests Session.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session |
requests.Session |
required |
Returns: The same session with coverage tracking enabled.
Example
import requests
session = coverage.requests.track_session(requests.Session())
response = session.get("https://api.example.com/users")
record
record(request, response=None, timestamp=None)
Record a request/response pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request |
requests.PreparedRequest |
required | |
response |
requests.Response | None |
None |
|
timestamp |
float | None |
Unix timestamp of the interaction | None |
Example
import requests
response = requests.get("https://api.example.com/users")
coverage.requests.record(request=response.request, response=response)
response_hook
response_hook()
Create a response hook for manual session configuration.
Returns: A ResponseHook callable.
Example
import requests
hook = coverage.requests.response_hook()
response = requests.get(
"https://api.example.com/users",
hooks={"response": [hook]}
)
HttpxPlugin
HttpxPlugin
Integration for the httpx library.
track_client
track_client(client)
Attach coverage tracking to an httpx Client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client |
httpx.Client | httpx.AsyncClient |
required |
Returns: The same client with coverage tracking enabled.
Example
import httpx
# Synchronous
client = coverage.httpx.track_client(httpx.Client())
response = client.get("https://api.example.com/users")
# Asynchronous
async def fetch_users():
async_client = coverage.httpx.track_client(httpx.AsyncClient())
return await async_client.get("https://api.example.com/users")
record
record(request, response=None, timestamp=None)
Record a request/response pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request |
httpx.Request |
required | |
response |
httpx.Response | None |
None |
|
timestamp |
float | None |
Unix timestamp of the interaction | None |
response_hook
response_hook()
Create a synchronous response hook.
Returns: A SyncResponseHook callable.
async_response_hook
async_response_hook()
Create an asynchronous response hook.
Returns: An AsyncResponseHook callable.
PostmanPlugin
PostmanPlugin
Professional Edition
Postman, HAR, and VCR import require TraceCov Professional.
In the Community edition installed by pip install tracecov, their methods raise ValueError.
Import traffic from Postman Collections (Professional).
record_from_path
record_from_path(path)
Import traffic from a Postman Collection file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path |
str | Path |
Path to Postman Collection JSON | required |
Example
coverage.postman.record_from_path("collection.json")
record_from_dict
record_from_dict(data)
Import traffic from a parsed Postman Collection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data |
dict |
Postman Collection as dictionary | required |
Supported formats: Postman Collection v1.0, v2.0, v2.1
HarPlugin
HarPlugin
Import traffic from HAR files (Professional).
record_from_path
record_from_path(path)
Import traffic from a HAR file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path |
str | Path |
Path to HAR file | required |
Example
coverage.har.record_from_path("traffic.har")
record_from_dict
record_from_dict(archive)
Import traffic from a parsed HAR archive.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
archive |
dict |
HAR archive as dictionary | required |
Supported formats: HAR v1.2, v1.3 from Chrome, Firefox, Edge, Postman.
VcrPlugin
VcrPlugin
Import traffic from VCR cassettes (Professional).
record_from_path
record_from_path(path)
Import traffic from a VCR cassette file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path |
str | Path |
Path to VCR cassette (YAML/JSON) | required |
record_from_dict
record_from_dict(cassette)
Import traffic from a parsed VCR cassette.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
cassette |
dict |
VCR cassette as dictionary | required |
Supported formats: Ruby VCR, Schemathesis cassettes.
DjangoPlugin
DjangoPlugin
Integration for Django test clients.
track_client
track_client(client)
Attach coverage tracking to a Django test client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client |
django.test.Client | django.test.AsyncClient | rest_framework.test.APIClient |
Django or DRF test client | required |
Returns: The same client with coverage tracking enabled.
Example
from django.test import Client
client = coverage.django.track_client(Client())
response = client.get("/api/users/")
record
record(request, response=None, timestamp=None, elapsed=None)
Record a Django request/response pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request |
django.http.HttpRequest |
Django request object | required |
response |
django.http.HttpResponse | None |
Django response object | None |
timestamp |
float | None |
Unix timestamp of the interaction | None |
elapsed |
float | None |
Response time in seconds | None |
FlaskPlugin
FlaskPlugin
Integration for Flask test clients.
track_client
track_client(client)
Attach coverage tracking to a Flask test client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client |
flask.testing.FlaskClient | werkzeug.test.Client |
Flask or Werkzeug test client | required |
Returns: The same client with coverage tracking enabled.
Example
from flask import Flask
app = Flask("myapp")
client = coverage.flask.track_client(app.test_client())
response = client.get("/api/users/")
record
record(request, response=None, timestamp=None, elapsed=None)
Record a Flask request/response pair.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request |
werkzeug.wrappers.Request |
Flask/Werkzeug request object | required |
response |
werkzeug.wrappers.Response | None |
Flask/Werkzeug response object | None |
timestamp |
float | None |
Unix timestamp of the interaction | None |
elapsed |
float | None |
Response time in seconds | None |
Low-Level Types
HttpRequest
HttpRequest(method, url, body=None, headers=None)
HTTP request for coverage tracking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
method |
str |
HTTP method (GET, POST, etc.) | required |
url |
str |
Full URL including query parameters | required |
body |
bytes | None |
Request body | None |
headers |
dict | None |
Request headers | None |
HttpResponse
HttpResponse(status_code, elapsed, body=None, headers=None)
HTTP response for coverage tracking. Without body and headers, response schema coverage is not measured.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
status_code |
int |
HTTP status code | required |
elapsed |
float |
Response time in seconds | required |
body |
bytes | None |
Response body | None |
headers |
dict | None |
Response headers, including Content-Type |
None |
HttpInteraction
HttpInteraction(request, response, timestamp)
A request, its response, and when it happened.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
request |
HttpRequest |
Request | required |
response |
HttpResponse | None |
Response, None when none was received |
required |
timestamp |
float |
Unix timestamp of the interaction | required |
parse_event_label
parse_event_label(label)
Parse a coverage event label, as returned by record_schemathesis_interactions(), into a CoverageEvent.
Labels are |-separated: tracecov|{domain}|{method}|{path}|{location}|{parameter}|{schema_path}.
Raises: ValueError when the label does not have seven segments or does not start with tracecov.
Example
import tracecov
event = tracecov.parse_event_label("tracecov|schema|GET|/users|query|limit|/minimum")
print(event.parameter, event.schema_path) # limit /minimum
labeled
labeled(*labels)
Context manager that credits every interaction recorded inside it to labels, on top of the labels already active. The labels follow the code into asyncio tasks but not into new threads. See Which Tests Cover What. Professional only; the Community edition emits TracecovEditionWarning on each recording and ignores the labels.
Example
import tracecov
with tracecov.labeled("suite:smoke"):
coverage.record(
tracecov.HttpRequest(
method="GET", url="https://api.example.com/test?id=1"
),
tracecov.HttpResponse(status_code=200, elapsed=0.1),
)
CoverageEvent
CoverageEvent
Frozen dataclass returned by parse_event_label(), with the label's segments as str attributes:
namespace, domain, method, path, location, parameter, schema_path.