schemathesis 3.21.2__py3-none-any.whl → 3.22.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- schemathesis/__init__.py +1 -1
- schemathesis/_compat.py +2 -18
- schemathesis/_dependency_versions.py +1 -6
- schemathesis/_hypothesis.py +15 -12
- schemathesis/_lazy_import.py +3 -2
- schemathesis/_xml.py +12 -11
- schemathesis/auths.py +88 -81
- schemathesis/checks.py +4 -4
- schemathesis/cli/__init__.py +202 -171
- schemathesis/cli/callbacks.py +29 -32
- schemathesis/cli/cassettes.py +25 -25
- schemathesis/cli/context.py +18 -12
- schemathesis/cli/junitxml.py +2 -2
- schemathesis/cli/options.py +10 -11
- schemathesis/cli/output/default.py +64 -34
- schemathesis/code_samples.py +10 -10
- schemathesis/constants.py +1 -1
- schemathesis/contrib/unique_data.py +2 -2
- schemathesis/exceptions.py +55 -42
- schemathesis/extra/_aiohttp.py +2 -2
- schemathesis/extra/_flask.py +2 -2
- schemathesis/extra/_server.py +3 -2
- schemathesis/extra/pytest_plugin.py +10 -10
- schemathesis/failures.py +16 -16
- schemathesis/filters.py +40 -41
- schemathesis/fixups/__init__.py +4 -3
- schemathesis/fixups/fast_api.py +5 -4
- schemathesis/generation/__init__.py +16 -4
- schemathesis/hooks.py +25 -25
- schemathesis/internal/jsonschema.py +4 -3
- schemathesis/internal/transformation.py +3 -2
- schemathesis/lazy.py +39 -31
- schemathesis/loaders.py +8 -8
- schemathesis/models.py +128 -126
- schemathesis/parameters.py +6 -5
- schemathesis/runner/__init__.py +107 -81
- schemathesis/runner/events.py +37 -26
- schemathesis/runner/impl/core.py +86 -81
- schemathesis/runner/impl/solo.py +19 -15
- schemathesis/runner/impl/threadpool.py +40 -22
- schemathesis/runner/serialization.py +67 -40
- schemathesis/sanitization.py +18 -20
- schemathesis/schemas.py +83 -72
- schemathesis/serializers.py +39 -30
- schemathesis/service/ci.py +20 -21
- schemathesis/service/client.py +29 -9
- schemathesis/service/constants.py +1 -0
- schemathesis/service/events.py +2 -2
- schemathesis/service/hosts.py +8 -7
- schemathesis/service/metadata.py +5 -0
- schemathesis/service/models.py +22 -4
- schemathesis/service/report.py +15 -15
- schemathesis/service/serialization.py +23 -27
- schemathesis/service/usage.py +8 -7
- schemathesis/specs/graphql/loaders.py +31 -24
- schemathesis/specs/graphql/nodes.py +3 -2
- schemathesis/specs/graphql/scalars.py +26 -2
- schemathesis/specs/graphql/schemas.py +38 -34
- schemathesis/specs/openapi/_hypothesis.py +62 -44
- schemathesis/specs/openapi/checks.py +10 -10
- schemathesis/specs/openapi/converter.py +10 -9
- schemathesis/specs/openapi/definitions.py +2 -2
- schemathesis/specs/openapi/examples.py +22 -21
- schemathesis/specs/openapi/expressions/nodes.py +5 -4
- schemathesis/specs/openapi/expressions/parser.py +7 -6
- schemathesis/specs/openapi/filters.py +6 -6
- schemathesis/specs/openapi/formats.py +2 -2
- schemathesis/specs/openapi/links.py +19 -21
- schemathesis/specs/openapi/loaders.py +133 -78
- schemathesis/specs/openapi/negative/__init__.py +16 -11
- schemathesis/specs/openapi/negative/mutations.py +11 -10
- schemathesis/specs/openapi/parameters.py +20 -19
- schemathesis/specs/openapi/references.py +21 -20
- schemathesis/specs/openapi/schemas.py +97 -84
- schemathesis/specs/openapi/security.py +25 -24
- schemathesis/specs/openapi/serialization.py +20 -23
- schemathesis/specs/openapi/stateful/__init__.py +12 -11
- schemathesis/specs/openapi/stateful/links.py +7 -7
- schemathesis/specs/openapi/utils.py +4 -3
- schemathesis/specs/openapi/validation.py +3 -2
- schemathesis/stateful/__init__.py +15 -16
- schemathesis/stateful/state_machine.py +9 -9
- schemathesis/targets.py +3 -3
- schemathesis/throttling.py +2 -2
- schemathesis/transports/auth.py +2 -2
- schemathesis/transports/content_types.py +5 -0
- schemathesis/transports/headers.py +3 -2
- schemathesis/transports/responses.py +1 -1
- schemathesis/utils.py +7 -10
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/METADATA +12 -13
- schemathesis-3.22.1.dist-info/RECORD +130 -0
- schemathesis-3.21.2.dist-info/RECORD +0 -130
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/WHEEL +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/licenses/LICENSE +0 -0
schemathesis/parameters.py
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
These are basic entities that describe what data could be sent to the API.
|
|
4
4
|
"""
|
|
5
|
+
from __future__ import annotations
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
|
-
from typing import TYPE_CHECKING, Any,
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Generator, Generic, TypeVar
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
9
10
|
from .models import APIOperation
|
|
@@ -43,7 +44,7 @@ class Parameter:
|
|
|
43
44
|
"""Parameter example."""
|
|
44
45
|
raise NotImplementedError
|
|
45
46
|
|
|
46
|
-
def serialize(self, operation:
|
|
47
|
+
def serialize(self, operation: APIOperation) -> str:
|
|
47
48
|
"""Get parameter's string representation."""
|
|
48
49
|
raise NotImplementedError
|
|
49
50
|
|
|
@@ -55,13 +56,13 @@ P = TypeVar("P", bound=Parameter)
|
|
|
55
56
|
class ParameterSet(Generic[P]):
|
|
56
57
|
"""A set of parameters for the same location."""
|
|
57
58
|
|
|
58
|
-
items:
|
|
59
|
+
items: list[P] = field(default_factory=list)
|
|
59
60
|
|
|
60
61
|
def add(self, parameter: P) -> None:
|
|
61
62
|
"""Add a new parameter."""
|
|
62
63
|
self.items.append(parameter)
|
|
63
64
|
|
|
64
|
-
def get(self, name: str) ->
|
|
65
|
+
def get(self, name: str) -> P | None:
|
|
65
66
|
for parameter in self:
|
|
66
67
|
if parameter.name == name:
|
|
67
68
|
return parameter
|
|
@@ -71,7 +72,7 @@ class ParameterSet(Generic[P]):
|
|
|
71
72
|
return self.get(name) is not None
|
|
72
73
|
|
|
73
74
|
@property
|
|
74
|
-
def example(self) ->
|
|
75
|
+
def example(self) -> dict[str, Any]:
|
|
75
76
|
"""Composite example gathered from individual parameters."""
|
|
76
77
|
return {item.name: item.example for item in self.items if item.example}
|
|
77
78
|
|
schemathesis/runner/__init__.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
from random import Random
|
|
4
|
+
from typing import Any, Callable, Generator, Iterable, TYPE_CHECKING
|
|
3
5
|
from urllib.parse import urlparse
|
|
4
6
|
|
|
5
|
-
from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod
|
|
7
|
+
from ..generation import DEFAULT_DATA_GENERATION_METHODS, DataGenerationMethod, GenerationConfig
|
|
6
8
|
from ..constants import (
|
|
7
9
|
DEFAULT_DEADLINE,
|
|
8
10
|
DEFAULT_STATEFUL_RECURSION_LIMIT,
|
|
@@ -30,46 +32,47 @@ if TYPE_CHECKING:
|
|
|
30
32
|
|
|
31
33
|
@deprecated_function(removed_in="4.0", replacement="schemathesis.runner.from_schema")
|
|
32
34
|
def prepare(
|
|
33
|
-
schema_uri:
|
|
35
|
+
schema_uri: str | dict[str, Any],
|
|
34
36
|
*,
|
|
35
37
|
# Runtime behavior
|
|
36
|
-
checks:
|
|
37
|
-
data_generation_methods:
|
|
38
|
-
max_response_time:
|
|
38
|
+
checks: Iterable[CheckFunction] | None = None,
|
|
39
|
+
data_generation_methods: tuple[DataGenerationMethod, ...] = DEFAULT_DATA_GENERATION_METHODS,
|
|
40
|
+
max_response_time: int | None = None,
|
|
39
41
|
targets: Iterable[Target] = DEFAULT_TARGETS,
|
|
40
42
|
workers_num: int = 1,
|
|
41
|
-
seed:
|
|
43
|
+
seed: int | None = None,
|
|
42
44
|
exit_first: bool = False,
|
|
43
45
|
dry_run: bool = False,
|
|
44
46
|
store_interactions: bool = False,
|
|
45
|
-
stateful:
|
|
47
|
+
stateful: Stateful | None = None,
|
|
46
48
|
stateful_recursion_limit: int = DEFAULT_STATEFUL_RECURSION_LIMIT,
|
|
47
49
|
# Schema loading
|
|
48
50
|
loader: Callable = oas_loaders.from_uri,
|
|
49
|
-
base_url:
|
|
50
|
-
auth:
|
|
51
|
-
auth_type:
|
|
52
|
-
headers:
|
|
53
|
-
request_timeout:
|
|
54
|
-
request_tls_verify:
|
|
55
|
-
request_cert:
|
|
56
|
-
endpoint:
|
|
57
|
-
method:
|
|
58
|
-
tag:
|
|
59
|
-
operation_id:
|
|
60
|
-
app:
|
|
51
|
+
base_url: str | None = None,
|
|
52
|
+
auth: tuple[str, str] | None = None,
|
|
53
|
+
auth_type: str | None = None,
|
|
54
|
+
headers: dict[str, str] | None = None,
|
|
55
|
+
request_timeout: int | None = None,
|
|
56
|
+
request_tls_verify: bool | str = True,
|
|
57
|
+
request_cert: RequestCert | None = None,
|
|
58
|
+
endpoint: Filter | None = None,
|
|
59
|
+
method: Filter | None = None,
|
|
60
|
+
tag: Filter | None = None,
|
|
61
|
+
operation_id: Filter | None = None,
|
|
62
|
+
app: str | None = None,
|
|
61
63
|
validate_schema: bool = True,
|
|
62
64
|
skip_deprecated_operations: bool = False,
|
|
63
|
-
force_schema_version:
|
|
65
|
+
force_schema_version: str | None = None,
|
|
64
66
|
count_operations: bool = True,
|
|
67
|
+
count_links: bool = True,
|
|
65
68
|
# Hypothesis-specific configuration
|
|
66
|
-
hypothesis_deadline:
|
|
67
|
-
hypothesis_derandomize:
|
|
68
|
-
hypothesis_max_examples:
|
|
69
|
-
hypothesis_phases:
|
|
70
|
-
hypothesis_report_multiple_bugs:
|
|
71
|
-
hypothesis_suppress_health_check:
|
|
72
|
-
hypothesis_verbosity:
|
|
69
|
+
hypothesis_deadline: int | NotSet | None = None,
|
|
70
|
+
hypothesis_derandomize: bool | None = None,
|
|
71
|
+
hypothesis_max_examples: int | None = None,
|
|
72
|
+
hypothesis_phases: list[hypothesis.Phase] | None = None,
|
|
73
|
+
hypothesis_report_multiple_bugs: bool | None = None,
|
|
74
|
+
hypothesis_suppress_health_check: list[hypothesis.HealthCheck] | None = None,
|
|
75
|
+
hypothesis_verbosity: hypothesis.Verbosity | None = None,
|
|
73
76
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
74
77
|
"""Prepare a generator that will run test cases against the given API definition."""
|
|
75
78
|
from ..checks import DEFAULT_CHECKS
|
|
@@ -121,10 +124,11 @@ def prepare(
|
|
|
121
124
|
stateful=stateful,
|
|
122
125
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
123
126
|
count_operations=count_operations,
|
|
127
|
+
count_links=count_links,
|
|
124
128
|
)
|
|
125
129
|
|
|
126
130
|
|
|
127
|
-
def validate_loader(loader: Callable, schema_uri:
|
|
131
|
+
def validate_loader(loader: Callable, schema_uri: str | dict[str, Any]) -> None:
|
|
128
132
|
"""Sanity checking for input schema & loader."""
|
|
129
133
|
if loader not in (
|
|
130
134
|
oas_loaders.from_uri,
|
|
@@ -149,36 +153,37 @@ def validate_loader(loader: Callable, schema_uri: Union[str, Dict[str, Any]]) ->
|
|
|
149
153
|
|
|
150
154
|
def execute_from_schema(
|
|
151
155
|
*,
|
|
152
|
-
schema_uri:
|
|
156
|
+
schema_uri: str | dict[str, Any],
|
|
153
157
|
loader: Callable = oas_loaders.from_uri,
|
|
154
|
-
base_url:
|
|
155
|
-
endpoint:
|
|
156
|
-
method:
|
|
157
|
-
tag:
|
|
158
|
-
operation_id:
|
|
159
|
-
app:
|
|
158
|
+
base_url: str | None = None,
|
|
159
|
+
endpoint: Filter | None = None,
|
|
160
|
+
method: Filter | None = None,
|
|
161
|
+
tag: Filter | None = None,
|
|
162
|
+
operation_id: Filter | None = None,
|
|
163
|
+
app: str | None = None,
|
|
160
164
|
validate_schema: bool = True,
|
|
161
165
|
skip_deprecated_operations: bool = False,
|
|
162
|
-
force_schema_version:
|
|
166
|
+
force_schema_version: str | None = None,
|
|
163
167
|
checks: Iterable[CheckFunction],
|
|
164
|
-
data_generation_methods:
|
|
165
|
-
max_response_time:
|
|
168
|
+
data_generation_methods: tuple[DataGenerationMethod, ...] = DEFAULT_DATA_GENERATION_METHODS,
|
|
169
|
+
max_response_time: int | None = None,
|
|
166
170
|
targets: Iterable[Target],
|
|
167
171
|
workers_num: int = 1,
|
|
168
172
|
hypothesis_settings: hypothesis.settings,
|
|
169
|
-
auth:
|
|
170
|
-
auth_type:
|
|
171
|
-
headers:
|
|
172
|
-
request_timeout:
|
|
173
|
-
request_tls_verify:
|
|
174
|
-
request_cert:
|
|
175
|
-
seed:
|
|
173
|
+
auth: RawAuth | None = None,
|
|
174
|
+
auth_type: str | None = None,
|
|
175
|
+
headers: dict[str, Any] | None = None,
|
|
176
|
+
request_timeout: int | None = None,
|
|
177
|
+
request_tls_verify: bool | str = True,
|
|
178
|
+
request_cert: RequestCert | None = None,
|
|
179
|
+
seed: int | None = None,
|
|
176
180
|
exit_first: bool = False,
|
|
177
181
|
dry_run: bool = False,
|
|
178
182
|
store_interactions: bool = False,
|
|
179
|
-
stateful:
|
|
183
|
+
stateful: Stateful | None = None,
|
|
180
184
|
stateful_recursion_limit: int = DEFAULT_STATEFUL_RECURSION_LIMIT,
|
|
181
185
|
count_operations: bool = True,
|
|
186
|
+
count_links: bool = True,
|
|
182
187
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
183
188
|
"""Execute tests for the given schema.
|
|
184
189
|
|
|
@@ -226,6 +231,7 @@ def execute_from_schema(
|
|
|
226
231
|
stateful=stateful,
|
|
227
232
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
228
233
|
count_operations=count_operations,
|
|
234
|
+
count_links=count_links,
|
|
229
235
|
).execute()
|
|
230
236
|
except SchemaError as error:
|
|
231
237
|
yield events.InternalError.from_schema_error(error)
|
|
@@ -234,26 +240,26 @@ def execute_from_schema(
|
|
|
234
240
|
|
|
235
241
|
|
|
236
242
|
def load_schema(
|
|
237
|
-
schema_uri:
|
|
243
|
+
schema_uri: str | dict[str, Any],
|
|
238
244
|
*,
|
|
239
|
-
base_url:
|
|
245
|
+
base_url: str | None = None,
|
|
240
246
|
loader: Callable = oas_loaders.from_uri,
|
|
241
247
|
app: Any = None,
|
|
242
248
|
validate_schema: bool = True,
|
|
243
249
|
skip_deprecated_operations: bool = False,
|
|
244
|
-
data_generation_methods:
|
|
245
|
-
force_schema_version:
|
|
246
|
-
request_tls_verify:
|
|
247
|
-
request_cert:
|
|
250
|
+
data_generation_methods: tuple[DataGenerationMethod, ...] = DEFAULT_DATA_GENERATION_METHODS,
|
|
251
|
+
force_schema_version: str | None = None,
|
|
252
|
+
request_tls_verify: bool | str = True,
|
|
253
|
+
request_cert: RequestCert | None = None,
|
|
248
254
|
# Network request parameters
|
|
249
|
-
auth:
|
|
250
|
-
auth_type:
|
|
251
|
-
headers:
|
|
255
|
+
auth: tuple[str, str] | None = None,
|
|
256
|
+
auth_type: str | None = None,
|
|
257
|
+
headers: dict[str, str] | None = None,
|
|
252
258
|
# Schema filters
|
|
253
|
-
endpoint:
|
|
254
|
-
method:
|
|
255
|
-
tag:
|
|
256
|
-
operation_id:
|
|
259
|
+
endpoint: Filter | None = None,
|
|
260
|
+
method: Filter | None = None,
|
|
261
|
+
tag: Filter | None = None,
|
|
262
|
+
operation_id: Filter | None = None,
|
|
257
263
|
) -> BaseSchema:
|
|
258
264
|
"""Load schema via specified loader and parameters."""
|
|
259
265
|
loader_options = {
|
|
@@ -306,26 +312,28 @@ def load_schema(
|
|
|
306
312
|
def from_schema(
|
|
307
313
|
schema: BaseSchema,
|
|
308
314
|
*,
|
|
309
|
-
checks:
|
|
310
|
-
max_response_time:
|
|
315
|
+
checks: Iterable[CheckFunction] | None = None,
|
|
316
|
+
max_response_time: int | None = None,
|
|
311
317
|
targets: Iterable[Target] = DEFAULT_TARGETS,
|
|
312
318
|
workers_num: int = 1,
|
|
313
|
-
hypothesis_settings:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
hypothesis_settings: hypothesis.settings | None = None,
|
|
320
|
+
generation_config: GenerationConfig | None = None,
|
|
321
|
+
auth: RawAuth | None = None,
|
|
322
|
+
auth_type: str | None = None,
|
|
323
|
+
headers: dict[str, Any] | None = None,
|
|
324
|
+
request_timeout: int | None = None,
|
|
325
|
+
request_tls_verify: bool | str = True,
|
|
326
|
+
request_cert: RequestCert | None = None,
|
|
327
|
+
seed: int | None = None,
|
|
321
328
|
exit_first: bool = False,
|
|
322
|
-
max_failures:
|
|
323
|
-
started_at:
|
|
329
|
+
max_failures: int | None = None,
|
|
330
|
+
started_at: str | None = None,
|
|
324
331
|
dry_run: bool = False,
|
|
325
332
|
store_interactions: bool = False,
|
|
326
|
-
stateful:
|
|
333
|
+
stateful: Stateful | None = None,
|
|
327
334
|
stateful_recursion_limit: int = DEFAULT_STATEFUL_RECURSION_LIMIT,
|
|
328
335
|
count_operations: bool = True,
|
|
336
|
+
count_links: bool = True,
|
|
329
337
|
) -> BaseRunner:
|
|
330
338
|
from starlette.applications import Starlette
|
|
331
339
|
import hypothesis
|
|
@@ -342,6 +350,12 @@ def from_schema(
|
|
|
342
350
|
checks = checks or DEFAULT_CHECKS
|
|
343
351
|
|
|
344
352
|
hypothesis_settings = hypothesis_settings or hypothesis.settings(deadline=DEFAULT_DEADLINE)
|
|
353
|
+
generation_config = generation_config or GenerationConfig()
|
|
354
|
+
|
|
355
|
+
# Use the same seed for all tests unless `derandomize=True` is used
|
|
356
|
+
if seed is None and not hypothesis_settings.derandomize:
|
|
357
|
+
seed = Random().getrandbits(128)
|
|
358
|
+
|
|
345
359
|
started_at = started_at or current_datetime()
|
|
346
360
|
if workers_num > 1:
|
|
347
361
|
if not schema.app:
|
|
@@ -351,6 +365,7 @@ def from_schema(
|
|
|
351
365
|
max_response_time=max_response_time,
|
|
352
366
|
targets=targets,
|
|
353
367
|
hypothesis_settings=hypothesis_settings,
|
|
368
|
+
generation_config=generation_config,
|
|
354
369
|
auth=auth,
|
|
355
370
|
auth_type=auth_type,
|
|
356
371
|
headers=headers,
|
|
@@ -367,6 +382,7 @@ def from_schema(
|
|
|
367
382
|
stateful=stateful,
|
|
368
383
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
369
384
|
count_operations=count_operations,
|
|
385
|
+
count_links=count_links,
|
|
370
386
|
)
|
|
371
387
|
if isinstance(schema.app, Starlette):
|
|
372
388
|
return ThreadPoolASGIRunner(
|
|
@@ -375,6 +391,7 @@ def from_schema(
|
|
|
375
391
|
max_response_time=max_response_time,
|
|
376
392
|
targets=targets,
|
|
377
393
|
hypothesis_settings=hypothesis_settings,
|
|
394
|
+
generation_config=generation_config,
|
|
378
395
|
auth=auth,
|
|
379
396
|
auth_type=auth_type,
|
|
380
397
|
headers=headers,
|
|
@@ -387,6 +404,7 @@ def from_schema(
|
|
|
387
404
|
stateful=stateful,
|
|
388
405
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
389
406
|
count_operations=count_operations,
|
|
407
|
+
count_links=count_links,
|
|
390
408
|
)
|
|
391
409
|
return ThreadPoolWSGIRunner(
|
|
392
410
|
schema=schema,
|
|
@@ -394,6 +412,7 @@ def from_schema(
|
|
|
394
412
|
max_response_time=max_response_time,
|
|
395
413
|
targets=targets,
|
|
396
414
|
hypothesis_settings=hypothesis_settings,
|
|
415
|
+
generation_config=generation_config,
|
|
397
416
|
auth=auth,
|
|
398
417
|
auth_type=auth_type,
|
|
399
418
|
headers=headers,
|
|
@@ -407,6 +426,7 @@ def from_schema(
|
|
|
407
426
|
stateful=stateful,
|
|
408
427
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
409
428
|
count_operations=count_operations,
|
|
429
|
+
count_links=count_links,
|
|
410
430
|
)
|
|
411
431
|
if not schema.app:
|
|
412
432
|
return SingleThreadRunner(
|
|
@@ -415,6 +435,7 @@ def from_schema(
|
|
|
415
435
|
max_response_time=max_response_time,
|
|
416
436
|
targets=targets,
|
|
417
437
|
hypothesis_settings=hypothesis_settings,
|
|
438
|
+
generation_config=generation_config,
|
|
418
439
|
auth=auth,
|
|
419
440
|
auth_type=auth_type,
|
|
420
441
|
headers=headers,
|
|
@@ -430,6 +451,7 @@ def from_schema(
|
|
|
430
451
|
stateful=stateful,
|
|
431
452
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
432
453
|
count_operations=count_operations,
|
|
454
|
+
count_links=count_links,
|
|
433
455
|
)
|
|
434
456
|
if isinstance(schema.app, Starlette):
|
|
435
457
|
return SingleThreadASGIRunner(
|
|
@@ -438,6 +460,7 @@ def from_schema(
|
|
|
438
460
|
max_response_time=max_response_time,
|
|
439
461
|
targets=targets,
|
|
440
462
|
hypothesis_settings=hypothesis_settings,
|
|
463
|
+
generation_config=generation_config,
|
|
441
464
|
auth=auth,
|
|
442
465
|
auth_type=auth_type,
|
|
443
466
|
headers=headers,
|
|
@@ -450,6 +473,7 @@ def from_schema(
|
|
|
450
473
|
stateful=stateful,
|
|
451
474
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
452
475
|
count_operations=count_operations,
|
|
476
|
+
count_links=count_links,
|
|
453
477
|
)
|
|
454
478
|
return SingleThreadWSGIRunner(
|
|
455
479
|
schema=schema,
|
|
@@ -457,6 +481,7 @@ def from_schema(
|
|
|
457
481
|
max_response_time=max_response_time,
|
|
458
482
|
targets=targets,
|
|
459
483
|
hypothesis_settings=hypothesis_settings,
|
|
484
|
+
generation_config=generation_config,
|
|
460
485
|
auth=auth,
|
|
461
486
|
auth_type=auth_type,
|
|
462
487
|
headers=headers,
|
|
@@ -469,18 +494,19 @@ def from_schema(
|
|
|
469
494
|
stateful=stateful,
|
|
470
495
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
471
496
|
count_operations=count_operations,
|
|
497
|
+
count_links=count_links,
|
|
472
498
|
)
|
|
473
499
|
|
|
474
500
|
|
|
475
501
|
def prepare_hypothesis_settings(
|
|
476
|
-
database:
|
|
477
|
-
deadline:
|
|
478
|
-
derandomize:
|
|
479
|
-
max_examples:
|
|
480
|
-
phases:
|
|
481
|
-
report_multiple_bugs:
|
|
482
|
-
suppress_health_check:
|
|
483
|
-
verbosity:
|
|
502
|
+
database: str | None = None,
|
|
503
|
+
deadline: int | NotSet | None = None,
|
|
504
|
+
derandomize: bool | None = None,
|
|
505
|
+
max_examples: int | None = None,
|
|
506
|
+
phases: list[hypothesis.Phase] | None = None,
|
|
507
|
+
report_multiple_bugs: bool | None = None,
|
|
508
|
+
suppress_health_check: list[hypothesis.HealthCheck] | None = None,
|
|
509
|
+
verbosity: hypothesis.Verbosity | None = None,
|
|
484
510
|
) -> hypothesis.settings:
|
|
485
511
|
import hypothesis
|
|
486
512
|
from hypothesis.database import DirectoryBasedExampleDatabase, InMemoryExampleDatabase
|
schemathesis/runner/events.py
CHANGED
|
@@ -3,7 +3,7 @@ import enum
|
|
|
3
3
|
import threading
|
|
4
4
|
import time
|
|
5
5
|
from dataclasses import asdict, dataclass, field
|
|
6
|
-
from typing import Any,
|
|
6
|
+
from typing import Any, TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from ..internal.datetime import current_datetime
|
|
9
9
|
from ..generation import DataGenerationMethod
|
|
@@ -23,7 +23,7 @@ class ExecutionEvent:
|
|
|
23
23
|
# Whether this event is expected to be the last one in the event stream
|
|
24
24
|
is_terminal = False
|
|
25
25
|
|
|
26
|
-
def asdict(self, **kwargs: Any) ->
|
|
26
|
+
def asdict(self, **kwargs: Any) -> dict[str, Any]:
|
|
27
27
|
data = asdict(self, **kwargs)
|
|
28
28
|
# An internal tag for simpler type identification
|
|
29
29
|
data["event_type"] = self.__class__.__name__
|
|
@@ -34,11 +34,14 @@ class ExecutionEvent:
|
|
|
34
34
|
class Initialized(ExecutionEvent):
|
|
35
35
|
"""Runner is initialized, settings are prepared, requests session is ready."""
|
|
36
36
|
|
|
37
|
-
schema:
|
|
37
|
+
schema: dict[str, Any]
|
|
38
38
|
# Total number of operations in the schema
|
|
39
|
-
operations_count:
|
|
39
|
+
operations_count: int | None
|
|
40
|
+
# Total number of links in the schema
|
|
41
|
+
links_count: int | None
|
|
40
42
|
# The place, where the API schema is located
|
|
41
|
-
location:
|
|
43
|
+
location: str | None
|
|
44
|
+
seed: int | None
|
|
42
45
|
# The base URL against which the tests are running
|
|
43
46
|
base_url: str
|
|
44
47
|
# API schema specification name
|
|
@@ -52,16 +55,24 @@ class Initialized(ExecutionEvent):
|
|
|
52
55
|
|
|
53
56
|
@classmethod
|
|
54
57
|
def from_schema(
|
|
55
|
-
cls,
|
|
56
|
-
|
|
58
|
+
cls,
|
|
59
|
+
*,
|
|
60
|
+
schema: BaseSchema,
|
|
61
|
+
count_operations: bool = True,
|
|
62
|
+
count_links: bool = True,
|
|
63
|
+
started_at: str | None = None,
|
|
64
|
+
seed: int | None,
|
|
65
|
+
) -> Initialized:
|
|
57
66
|
"""Computes all needed data from a schema instance."""
|
|
58
67
|
return cls(
|
|
59
68
|
schema=schema.raw_schema,
|
|
60
69
|
operations_count=schema.operations_count if count_operations else None,
|
|
70
|
+
links_count=schema.links_count if count_links else None,
|
|
61
71
|
location=schema.location,
|
|
62
72
|
base_url=schema.get_base_url(),
|
|
63
73
|
started_at=started_at or current_datetime(),
|
|
64
74
|
specification_name=schema.verbose_name,
|
|
75
|
+
seed=seed,
|
|
65
76
|
)
|
|
66
77
|
|
|
67
78
|
|
|
@@ -92,7 +103,7 @@ class BeforeExecution(CurrentOperationMixin, ExecutionEvent):
|
|
|
92
103
|
# The current level of recursion during stateful testing
|
|
93
104
|
recursion_level: int
|
|
94
105
|
# The way data will be generated
|
|
95
|
-
data_generation_method:
|
|
106
|
+
data_generation_method: list[DataGenerationMethod]
|
|
96
107
|
# A unique ID which connects events that happen during testing of the same API operation
|
|
97
108
|
# It may be useful when multiple threads are involved where incoming events are not ordered
|
|
98
109
|
correlation_id: str
|
|
@@ -103,9 +114,9 @@ class BeforeExecution(CurrentOperationMixin, ExecutionEvent):
|
|
|
103
114
|
cls,
|
|
104
115
|
operation: APIOperation,
|
|
105
116
|
recursion_level: int,
|
|
106
|
-
data_generation_method:
|
|
117
|
+
data_generation_method: list[DataGenerationMethod],
|
|
107
118
|
correlation_id: str,
|
|
108
|
-
) ->
|
|
119
|
+
) -> BeforeExecution:
|
|
109
120
|
return cls(
|
|
110
121
|
method=operation.method.upper(),
|
|
111
122
|
path=operation.full_path,
|
|
@@ -130,14 +141,14 @@ class AfterExecution(CurrentOperationMixin, ExecutionEvent):
|
|
|
130
141
|
# APIOperation test status - success / failure / error
|
|
131
142
|
status: Status
|
|
132
143
|
# The way data was generated
|
|
133
|
-
data_generation_method:
|
|
144
|
+
data_generation_method: list[DataGenerationMethod]
|
|
134
145
|
result: SerializedTestResult
|
|
135
146
|
# Test running time
|
|
136
147
|
elapsed_time: float
|
|
137
148
|
correlation_id: str
|
|
138
149
|
thread_id: int = field(default_factory=threading.get_ident)
|
|
139
150
|
# Captured hypothesis stdout
|
|
140
|
-
hypothesis_output:
|
|
151
|
+
hypothesis_output: list[str] = field(default_factory=list)
|
|
141
152
|
|
|
142
153
|
@classmethod
|
|
143
154
|
def from_result(
|
|
@@ -145,11 +156,11 @@ class AfterExecution(CurrentOperationMixin, ExecutionEvent):
|
|
|
145
156
|
result: TestResult,
|
|
146
157
|
status: Status,
|
|
147
158
|
elapsed_time: float,
|
|
148
|
-
hypothesis_output:
|
|
159
|
+
hypothesis_output: list[str],
|
|
149
160
|
operation: APIOperation,
|
|
150
|
-
data_generation_method:
|
|
161
|
+
data_generation_method: list[DataGenerationMethod],
|
|
151
162
|
correlation_id: str,
|
|
152
|
-
) ->
|
|
163
|
+
) -> AfterExecution:
|
|
153
164
|
return cls(
|
|
154
165
|
method=operation.method.upper(),
|
|
155
166
|
path=operation.full_path,
|
|
@@ -185,10 +196,10 @@ class InternalError(ExecutionEvent):
|
|
|
185
196
|
|
|
186
197
|
# Main error info
|
|
187
198
|
type: InternalErrorType
|
|
188
|
-
subtype:
|
|
199
|
+
subtype: SchemaErrorType | None
|
|
189
200
|
title: str
|
|
190
201
|
message: str
|
|
191
|
-
extras:
|
|
202
|
+
extras: list[str]
|
|
192
203
|
|
|
193
204
|
# Exception info
|
|
194
205
|
exception_type: str
|
|
@@ -198,7 +209,7 @@ class InternalError(ExecutionEvent):
|
|
|
198
209
|
thread_id: int = field(default_factory=threading.get_ident)
|
|
199
210
|
|
|
200
211
|
@classmethod
|
|
201
|
-
def from_schema_error(cls, error: SchemaError) ->
|
|
212
|
+
def from_schema_error(cls, error: SchemaError) -> InternalError:
|
|
202
213
|
return cls.with_exception(
|
|
203
214
|
error,
|
|
204
215
|
type_=InternalErrorType.SCHEMA,
|
|
@@ -209,7 +220,7 @@ class InternalError(ExecutionEvent):
|
|
|
209
220
|
)
|
|
210
221
|
|
|
211
222
|
@classmethod
|
|
212
|
-
def from_exc(cls, exc: Exception) ->
|
|
223
|
+
def from_exc(cls, exc: Exception) -> InternalError:
|
|
213
224
|
return cls.with_exception(
|
|
214
225
|
exc,
|
|
215
226
|
type_=InternalErrorType.OTHER,
|
|
@@ -224,11 +235,11 @@ class InternalError(ExecutionEvent):
|
|
|
224
235
|
cls,
|
|
225
236
|
exc: Exception,
|
|
226
237
|
type_: InternalErrorType,
|
|
227
|
-
subtype:
|
|
238
|
+
subtype: SchemaErrorType | None,
|
|
228
239
|
title: str,
|
|
229
240
|
message: str,
|
|
230
|
-
extras:
|
|
231
|
-
) ->
|
|
241
|
+
extras: list[str],
|
|
242
|
+
) -> InternalError:
|
|
232
243
|
exception_type = f"{exc.__class__.__module__}.{exc.__class__.__qualname__}"
|
|
233
244
|
exception = format_exception(exc)
|
|
234
245
|
exception_with_traceback = format_exception(exc, include_traceback=True)
|
|
@@ -262,17 +273,17 @@ class Finished(ExecutionEvent):
|
|
|
262
273
|
has_errors: bool
|
|
263
274
|
has_logs: bool
|
|
264
275
|
is_empty: bool
|
|
265
|
-
generic_errors:
|
|
266
|
-
warnings:
|
|
276
|
+
generic_errors: list[SerializedError]
|
|
277
|
+
warnings: list[str]
|
|
267
278
|
|
|
268
|
-
total:
|
|
279
|
+
total: dict[str, dict[str | Status, int]]
|
|
269
280
|
|
|
270
281
|
# Total test run execution time
|
|
271
282
|
running_time: float
|
|
272
283
|
thread_id: int = field(default_factory=threading.get_ident)
|
|
273
284
|
|
|
274
285
|
@classmethod
|
|
275
|
-
def from_results(cls, results: TestResultSet, running_time: float) ->
|
|
286
|
+
def from_results(cls, results: TestResultSet, running_time: float) -> Finished:
|
|
276
287
|
return cls(
|
|
277
288
|
passed_count=results.passed_count,
|
|
278
289
|
skipped_count=results.skipped_count,
|