schemathesis 3.35.0__py3-none-any.whl → 3.35.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/_hypothesis.py +36 -10
- schemathesis/cli/cassettes.py +2 -0
- schemathesis/generation/coverage.py +149 -95
- schemathesis/models.py +14 -1
- schemathesis/runner/serialization.py +3 -1
- schemathesis/specs/openapi/_hypothesis.py +3 -1
- schemathesis/specs/openapi/examples.py +3 -1
- {schemathesis-3.35.0.dist-info → schemathesis-3.35.1.dist-info}/METADATA +1 -1
- {schemathesis-3.35.0.dist-info → schemathesis-3.35.1.dist-info}/RECORD +12 -12
- {schemathesis-3.35.0.dist-info → schemathesis-3.35.1.dist-info}/WHEEL +0 -0
- {schemathesis-3.35.0.dist-info → schemathesis-3.35.1.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.35.0.dist-info → schemathesis-3.35.1.dist-info}/licenses/LICENSE +0 -0
schemathesis/_hypothesis.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
|
+
import json
|
|
6
7
|
import warnings
|
|
7
8
|
from typing import Any, Callable, Generator, Mapping, Optional, Tuple
|
|
8
9
|
|
|
@@ -19,9 +20,10 @@ from .exceptions import OperationSchemaError, SerializationNotPossible
|
|
|
19
20
|
from .experimental import COVERAGE_PHASE
|
|
20
21
|
from .generation import DataGenerationMethod, GenerationConfig, combine_strategies, coverage, get_single_example
|
|
21
22
|
from .hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher
|
|
22
|
-
from .models import APIOperation, Case
|
|
23
|
+
from .models import APIOperation, Case, GenerationMetadata, TestPhase
|
|
23
24
|
from .transports.content_types import parse_content_type
|
|
24
25
|
from .transports.headers import has_invalid_characters, is_latin_1_encodable
|
|
26
|
+
from .types import NotSet
|
|
25
27
|
from .utils import GivenInput
|
|
26
28
|
|
|
27
29
|
# Forcefully initializes Hypothesis' global PRNG to avoid races that initilize it
|
|
@@ -215,39 +217,63 @@ def _iter_coverage_cases(
|
|
|
215
217
|
from .specs.openapi.constants import LOCATION_TO_CONTAINER
|
|
216
218
|
|
|
217
219
|
ctx = coverage.CoverageContext(data_generation_methods=data_generation_methods)
|
|
218
|
-
|
|
220
|
+
meta = GenerationMetadata(
|
|
221
|
+
query=None, path_parameters=None, headers=None, cookies=None, body=None, phase=TestPhase.COVERAGE
|
|
222
|
+
)
|
|
223
|
+
generators: dict[tuple[str, str], Generator[coverage.GeneratedValue, None, None]] = {}
|
|
219
224
|
template: dict[str, Any] = {}
|
|
225
|
+
template_generation_method = DataGenerationMethod.positive
|
|
220
226
|
for parameter in operation.iter_parameters():
|
|
221
227
|
schema = parameter.as_json_schema(operation)
|
|
222
228
|
gen = coverage.cover_schema_iter(ctx, schema)
|
|
223
229
|
value = next(gen, NOT_SET)
|
|
224
|
-
if value
|
|
230
|
+
if isinstance(value, NotSet):
|
|
225
231
|
continue
|
|
226
232
|
location = parameter.location
|
|
227
233
|
name = parameter.name
|
|
228
234
|
container = template.setdefault(LOCATION_TO_CONTAINER[location], {})
|
|
229
|
-
|
|
235
|
+
if location in ("header", "cookie") and not isinstance(value.value, str):
|
|
236
|
+
container[name] = json.dumps(value.value)
|
|
237
|
+
else:
|
|
238
|
+
container[name] = value.value
|
|
239
|
+
template_generation_method = value.data_generation_method
|
|
230
240
|
generators[(location, name)] = gen
|
|
231
241
|
if operation.body:
|
|
232
242
|
for body in operation.body:
|
|
233
243
|
schema = body.as_json_schema(operation)
|
|
234
244
|
gen = coverage.cover_schema_iter(ctx, schema)
|
|
235
245
|
value = next(gen, NOT_SET)
|
|
236
|
-
if value
|
|
246
|
+
if isinstance(value, NotSet):
|
|
237
247
|
continue
|
|
238
248
|
if "body" not in template:
|
|
239
|
-
template["body"] = value
|
|
249
|
+
template["body"] = value.value
|
|
240
250
|
template["media_type"] = body.media_type
|
|
241
|
-
|
|
251
|
+
case = operation.make_case(**{**template, "body": value.value, "media_type": body.media_type})
|
|
252
|
+
case.data_generation_method = value.data_generation_method
|
|
253
|
+
case.meta = meta
|
|
254
|
+
yield case
|
|
242
255
|
for next_value in gen:
|
|
243
|
-
|
|
256
|
+
case = operation.make_case(**{**template, "body": next_value.value, "media_type": body.media_type})
|
|
257
|
+
case.data_generation_method = next_value.data_generation_method
|
|
258
|
+
case.meta = meta
|
|
259
|
+
yield case
|
|
244
260
|
else:
|
|
245
|
-
|
|
261
|
+
case = operation.make_case(**template)
|
|
262
|
+
case.data_generation_method = template_generation_method
|
|
263
|
+
case.meta = meta
|
|
264
|
+
yield case
|
|
246
265
|
for (location, name), gen in generators.items():
|
|
247
266
|
container_name = LOCATION_TO_CONTAINER[location]
|
|
248
267
|
container = template[container_name]
|
|
249
268
|
for value in gen:
|
|
250
|
-
|
|
269
|
+
if location in ("header", "cookie") and not isinstance(value.value, str):
|
|
270
|
+
generated = json.dumps(value.value)
|
|
271
|
+
else:
|
|
272
|
+
generated = value.value
|
|
273
|
+
case = operation.make_case(**{**template, container_name: {**container, name: generated}})
|
|
274
|
+
case.data_generation_method = value.data_generation_method
|
|
275
|
+
case.meta = meta
|
|
276
|
+
yield case
|
|
251
277
|
|
|
252
278
|
|
|
253
279
|
def find_invalid_headers(headers: Mapping) -> Generator[Tuple[str, str], None, None]:
|
schemathesis/cli/cassettes.py
CHANGED
|
@@ -226,6 +226,7 @@ http_interactions:"""
|
|
|
226
226
|
for interaction in item.interactions:
|
|
227
227
|
status = interaction.status.name.upper()
|
|
228
228
|
# Body payloads are handled via separate `stream.write` calls to avoid some allocations
|
|
229
|
+
phase = f"'{interaction.phase.value}'" if interaction.phase is not None else "null"
|
|
229
230
|
stream.write(
|
|
230
231
|
f"""\n- id: '{current_id}'
|
|
231
232
|
status: '{status}'
|
|
@@ -233,6 +234,7 @@ http_interactions:"""
|
|
|
233
234
|
thread_id: {item.thread_id}
|
|
234
235
|
correlation_id: '{item.correlation_id}'
|
|
235
236
|
data_generation_method: '{interaction.data_generation_method.value}'
|
|
237
|
+
phase: {phase}
|
|
236
238
|
elapsed: '{interaction.response.elapsed}'
|
|
237
239
|
recorded_at: '{interaction.recorded_at}'
|
|
238
240
|
checks:
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from contextlib import suppress
|
|
4
|
+
from contextlib import contextmanager, suppress
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from functools import lru_cache
|
|
7
|
-
from typing import Any, Generator, Set, TypeVar, cast
|
|
7
|
+
from typing import Any, Generator, Set, Type, TypeVar, cast
|
|
8
8
|
|
|
9
9
|
import jsonschema
|
|
10
10
|
from hypothesis import strategies as st
|
|
11
|
-
from hypothesis.errors import Unsatisfiable
|
|
11
|
+
from hypothesis.errors import InvalidArgument, Unsatisfiable
|
|
12
12
|
from hypothesis_jsonschema import from_schema
|
|
13
13
|
from hypothesis_jsonschema._canonicalise import canonicalish
|
|
14
14
|
|
|
15
|
+
from schemathesis.constants import NOT_SET
|
|
16
|
+
|
|
15
17
|
from ._hypothesis import combine_strategies, get_single_example
|
|
16
18
|
from ._methods import DataGenerationMethod
|
|
17
19
|
|
|
@@ -29,6 +31,26 @@ UNKNOWN_PROPERTY_KEY = "x-schemathesis-unknown-property"
|
|
|
29
31
|
UNKNOWN_PROPERTY_VALUE = 42
|
|
30
32
|
|
|
31
33
|
|
|
34
|
+
@dataclass
|
|
35
|
+
class GeneratedValue:
|
|
36
|
+
value: Any
|
|
37
|
+
data_generation_method: DataGenerationMethod
|
|
38
|
+
|
|
39
|
+
__slots__ = ("value", "data_generation_method")
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def with_positive(cls, value: Any) -> GeneratedValue:
|
|
43
|
+
return cls(value, DataGenerationMethod.positive)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def with_negative(cls, value: Any) -> GeneratedValue:
|
|
47
|
+
return cls(value, DataGenerationMethod.negative)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
PositiveValue = GeneratedValue.with_positive
|
|
51
|
+
NegativeValue = GeneratedValue.with_negative
|
|
52
|
+
|
|
53
|
+
|
|
32
54
|
@lru_cache(maxsize=128)
|
|
33
55
|
def cached_draw(strategy: st.SearchStrategy) -> Any:
|
|
34
56
|
return get_single_example(strategy)
|
|
@@ -67,11 +89,9 @@ def _to_hashable_key(value: T) -> T | tuple[type, str]:
|
|
|
67
89
|
return value
|
|
68
90
|
|
|
69
91
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def _cover_positive_for_type(ctx: CoverageContext, schema: dict, ty: str | None) -> Generator:
|
|
92
|
+
def _cover_positive_for_type(
|
|
93
|
+
ctx: CoverageContext, schema: dict, ty: str | None
|
|
94
|
+
) -> Generator[GeneratedValue, None, None]:
|
|
75
95
|
if ty == "object" or ty == "array":
|
|
76
96
|
template_schema = _get_template_schema(schema, ty)
|
|
77
97
|
template = ctx.generate_from_schema(template_schema)
|
|
@@ -79,8 +99,8 @@ def _cover_positive_for_type(ctx: CoverageContext, schema: dict, ty: str | None)
|
|
|
79
99
|
template = None
|
|
80
100
|
if DataGenerationMethod.positive in ctx.data_generation_methods:
|
|
81
101
|
ctx = ctx.with_positive()
|
|
82
|
-
enum = schema.get("enum")
|
|
83
|
-
const = schema.get("const")
|
|
102
|
+
enum = schema.get("enum", NOT_SET)
|
|
103
|
+
const = schema.get("const", NOT_SET)
|
|
84
104
|
for key in ("anyOf", "oneOf"):
|
|
85
105
|
sub_schemas = schema.get(key)
|
|
86
106
|
if sub_schemas is not None:
|
|
@@ -91,43 +111,70 @@ def _cover_positive_for_type(ctx: CoverageContext, schema: dict, ty: str | None)
|
|
|
91
111
|
if len(all_of) == 1:
|
|
92
112
|
yield from cover_schema_iter(ctx, all_of[0])
|
|
93
113
|
else:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
114
|
+
with suppress(jsonschema.SchemaError):
|
|
115
|
+
canonical = canonicalish(schema)
|
|
116
|
+
yield from cover_schema_iter(ctx, canonical)
|
|
117
|
+
if enum is not NOT_SET:
|
|
118
|
+
for value in enum:
|
|
119
|
+
yield PositiveValue(value)
|
|
120
|
+
elif const is not NOT_SET:
|
|
121
|
+
yield PositiveValue(const)
|
|
100
122
|
elif ty is not None:
|
|
101
123
|
if ty == "null":
|
|
102
|
-
yield None
|
|
103
|
-
|
|
104
|
-
yield True
|
|
105
|
-
yield False
|
|
106
|
-
|
|
124
|
+
yield PositiveValue(None)
|
|
125
|
+
elif ty == "boolean":
|
|
126
|
+
yield PositiveValue(True)
|
|
127
|
+
yield PositiveValue(False)
|
|
128
|
+
elif ty == "string":
|
|
107
129
|
yield from _positive_string(ctx, schema)
|
|
108
|
-
|
|
130
|
+
elif ty == "integer" or ty == "number":
|
|
109
131
|
yield from _positive_number(ctx, schema)
|
|
110
|
-
|
|
132
|
+
elif ty == "array":
|
|
111
133
|
yield from _positive_array(ctx, schema, cast(list, template))
|
|
112
|
-
|
|
134
|
+
elif ty == "object":
|
|
113
135
|
yield from _positive_object(ctx, schema, cast(dict, template))
|
|
114
136
|
|
|
115
137
|
|
|
116
|
-
|
|
117
|
-
|
|
138
|
+
@contextmanager
|
|
139
|
+
def _ignore_unfixable(
|
|
140
|
+
*,
|
|
141
|
+
# Cache exception types here as `jsonschema` uses a custom `__getattr__` on the module level
|
|
142
|
+
# and it may cause errors during the interpreter shutdown
|
|
143
|
+
ref_error: Type[Exception] = jsonschema.RefResolutionError,
|
|
144
|
+
schema_error: Type[Exception] = jsonschema.SchemaError,
|
|
145
|
+
) -> Generator:
|
|
146
|
+
try:
|
|
147
|
+
yield
|
|
148
|
+
except (Unsatisfiable, ref_error, schema_error):
|
|
149
|
+
pass
|
|
150
|
+
except InvalidArgument as exc:
|
|
151
|
+
message = str(exc)
|
|
152
|
+
if "Cannot create non-empty" not in message and "is not in the specified alphabet" not in message:
|
|
153
|
+
raise
|
|
154
|
+
except TypeError as exc:
|
|
155
|
+
if "first argument must be string or compiled pattern" not in str(exc):
|
|
156
|
+
raise
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[GeneratedValue, None, None]:
|
|
160
|
+
if isinstance(schema, bool):
|
|
161
|
+
types = ["null", "boolean", "string", "number", "array", "object"]
|
|
162
|
+
schema = {}
|
|
163
|
+
else:
|
|
164
|
+
types = schema.get("type", [])
|
|
118
165
|
if not isinstance(types, list):
|
|
119
|
-
types = [types]
|
|
166
|
+
types = [types] # type: ignore[unreachable]
|
|
120
167
|
if not types:
|
|
121
|
-
with
|
|
168
|
+
with _ignore_unfixable():
|
|
122
169
|
yield from _cover_positive_for_type(ctx, schema, None)
|
|
123
170
|
for ty in types:
|
|
124
|
-
with
|
|
171
|
+
with _ignore_unfixable():
|
|
125
172
|
yield from _cover_positive_for_type(ctx, schema, ty)
|
|
126
173
|
if DataGenerationMethod.negative in ctx.data_generation_methods:
|
|
127
174
|
template = None
|
|
128
175
|
seen: Set[Any | tuple[type, str]] = set()
|
|
129
176
|
for key, value in schema.items():
|
|
130
|
-
with
|
|
177
|
+
with _ignore_unfixable():
|
|
131
178
|
if key == "enum":
|
|
132
179
|
yield from _negative_enum(ctx, value)
|
|
133
180
|
elif key == "const":
|
|
@@ -143,21 +190,27 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
143
190
|
yield from _negative_format(ctx, schema, value)
|
|
144
191
|
elif key == "maximum":
|
|
145
192
|
next = value + 1
|
|
146
|
-
yield next
|
|
193
|
+
yield NegativeValue(next)
|
|
147
194
|
seen.add(next)
|
|
148
195
|
elif key == "minimum":
|
|
149
196
|
next = value - 1
|
|
150
|
-
yield next
|
|
197
|
+
yield NegativeValue(next)
|
|
151
198
|
seen.add(next)
|
|
152
199
|
elif key == "exclusiveMaximum" or key == "exclusiveMinimum" and value not in seen:
|
|
153
|
-
yield value
|
|
200
|
+
yield NegativeValue(value)
|
|
154
201
|
seen.add(value)
|
|
155
202
|
elif key == "multipleOf":
|
|
156
203
|
yield from _negative_multiple_of(ctx, schema, value)
|
|
157
204
|
elif key == "minLength" and 0 < value < BUFFER_SIZE and "pattern" not in schema:
|
|
158
|
-
|
|
205
|
+
with suppress(InvalidArgument):
|
|
206
|
+
yield NegativeValue(
|
|
207
|
+
ctx.generate_from_schema({**schema, "minLength": value - 1, "maxLength": value - 1})
|
|
208
|
+
)
|
|
159
209
|
elif key == "maxLength" and value < BUFFER_SIZE and "pattern" not in schema:
|
|
160
|
-
|
|
210
|
+
with suppress(InvalidArgument):
|
|
211
|
+
yield NegativeValue(
|
|
212
|
+
ctx.generate_from_schema({**schema, "minLength": value + 1, "maxLength": value + 1})
|
|
213
|
+
)
|
|
161
214
|
elif key == "uniqueItems" and value:
|
|
162
215
|
yield from _negative_unique_items(ctx, schema)
|
|
163
216
|
elif key == "required":
|
|
@@ -165,14 +218,15 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
165
218
|
yield from _negative_required(ctx, template, value)
|
|
166
219
|
elif key == "additionalProperties" and not value:
|
|
167
220
|
template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
|
|
168
|
-
yield {**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE}
|
|
221
|
+
yield NegativeValue({**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE})
|
|
169
222
|
elif key == "allOf":
|
|
170
223
|
nctx = ctx.with_negative()
|
|
171
224
|
if len(value) == 1:
|
|
172
225
|
yield from cover_schema_iter(nctx, value[0])
|
|
173
226
|
else:
|
|
174
|
-
|
|
175
|
-
|
|
227
|
+
with _ignore_unfixable():
|
|
228
|
+
canonical = canonicalish(schema)
|
|
229
|
+
yield from cover_schema_iter(nctx, canonical)
|
|
176
230
|
elif key == "anyOf" or key == "oneOf":
|
|
177
231
|
nctx = ctx.with_negative()
|
|
178
232
|
# NOTE: Other sub-schemas are not filtered out
|
|
@@ -189,14 +243,14 @@ def _get_template_schema(schema: dict, ty: str) -> dict:
|
|
|
189
243
|
"required": list(properties),
|
|
190
244
|
"type": ty,
|
|
191
245
|
"properties": {
|
|
192
|
-
k: _get_template_schema(v, "object") if v.get("type") == "object" else v
|
|
246
|
+
k: _get_template_schema(v, "object") if isinstance(v, dict) and v.get("type") == "object" else v
|
|
193
247
|
for k, v in properties.items()
|
|
194
248
|
},
|
|
195
249
|
}
|
|
196
250
|
return {**schema, "type": ty}
|
|
197
251
|
|
|
198
252
|
|
|
199
|
-
def _positive_string(ctx: CoverageContext, schema: dict) -> Generator:
|
|
253
|
+
def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
|
|
200
254
|
"""Generate positive string values."""
|
|
201
255
|
# Boundary and near boundary values
|
|
202
256
|
min_length = schema.get("minLength")
|
|
@@ -204,25 +258,25 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
204
258
|
|
|
205
259
|
if not min_length and not max_length:
|
|
206
260
|
# Default positive value
|
|
207
|
-
yield ctx.generate_from_schema(schema)
|
|
261
|
+
yield PositiveValue(ctx.generate_from_schema(schema))
|
|
208
262
|
|
|
209
263
|
seen = set()
|
|
210
264
|
|
|
211
265
|
if min_length is not None and min_length < BUFFER_SIZE and "pattern" not in schema:
|
|
212
266
|
# Exactly the minimum length
|
|
213
|
-
yield ctx.generate_from_schema({**schema, "maxLength": min_length})
|
|
267
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "maxLength": min_length}))
|
|
214
268
|
seen.add(min_length)
|
|
215
269
|
|
|
216
270
|
# One character more than minimum if possible
|
|
217
271
|
larger = min_length + 1
|
|
218
272
|
if larger < BUFFER_SIZE and larger not in seen and (not max_length or larger <= max_length):
|
|
219
|
-
yield ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger})
|
|
273
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}))
|
|
220
274
|
seen.add(larger)
|
|
221
275
|
|
|
222
276
|
if max_length is not None and "pattern" not in schema:
|
|
223
277
|
# Exactly the maximum length
|
|
224
278
|
if max_length < BUFFER_SIZE and max_length not in seen:
|
|
225
|
-
yield ctx.generate_from_schema({**schema, "minLength": max_length})
|
|
279
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": max_length}))
|
|
226
280
|
seen.add(max_length)
|
|
227
281
|
|
|
228
282
|
# One character less than maximum if possible
|
|
@@ -232,7 +286,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
232
286
|
and smaller not in seen
|
|
233
287
|
and (smaller > 0 and (min_length is None or smaller >= min_length))
|
|
234
288
|
):
|
|
235
|
-
yield ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller})
|
|
289
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller}))
|
|
236
290
|
seen.add(smaller)
|
|
237
291
|
|
|
238
292
|
|
|
@@ -244,7 +298,7 @@ def closest_multiple_greater_than(y: int, x: int) -> int:
|
|
|
244
298
|
return x * (quotient + 1)
|
|
245
299
|
|
|
246
300
|
|
|
247
|
-
def _positive_number(ctx: CoverageContext, schema: dict) -> Generator:
|
|
301
|
+
def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
|
|
248
302
|
"""Generate positive integer values."""
|
|
249
303
|
# Boundary and near boundary values
|
|
250
304
|
minimum = schema.get("minimum")
|
|
@@ -259,7 +313,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
259
313
|
|
|
260
314
|
if not minimum and not maximum:
|
|
261
315
|
# Default positive value
|
|
262
|
-
yield ctx.generate_from_schema(schema)
|
|
316
|
+
yield PositiveValue(ctx.generate_from_schema(schema))
|
|
263
317
|
|
|
264
318
|
seen = set()
|
|
265
319
|
|
|
@@ -270,7 +324,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
270
324
|
else:
|
|
271
325
|
smallest = minimum
|
|
272
326
|
seen.add(smallest)
|
|
273
|
-
yield smallest
|
|
327
|
+
yield PositiveValue(smallest)
|
|
274
328
|
|
|
275
329
|
# One more than minimum if possible
|
|
276
330
|
if multiple_of is not None:
|
|
@@ -279,7 +333,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
279
333
|
larger = minimum + 1
|
|
280
334
|
if larger not in seen and (not maximum or larger <= maximum):
|
|
281
335
|
seen.add(larger)
|
|
282
|
-
yield larger
|
|
336
|
+
yield PositiveValue(larger)
|
|
283
337
|
|
|
284
338
|
if maximum is not None:
|
|
285
339
|
# Exactly the maximum
|
|
@@ -289,7 +343,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
289
343
|
largest = maximum
|
|
290
344
|
if largest not in seen:
|
|
291
345
|
seen.add(largest)
|
|
292
|
-
yield largest
|
|
346
|
+
yield PositiveValue(largest)
|
|
293
347
|
|
|
294
348
|
# One less than maximum if possible
|
|
295
349
|
if multiple_of is not None:
|
|
@@ -298,12 +352,12 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator:
|
|
|
298
352
|
smaller = maximum - 1
|
|
299
353
|
if smaller not in seen and (smaller > 0 and (minimum is None or smaller >= minimum)):
|
|
300
354
|
seen.add(smaller)
|
|
301
|
-
yield smaller
|
|
355
|
+
yield PositiveValue(smaller)
|
|
302
356
|
|
|
303
357
|
|
|
304
|
-
def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Generator:
|
|
358
|
+
def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Generator[GeneratedValue, None, None]:
|
|
305
359
|
seen = set()
|
|
306
|
-
yield template
|
|
360
|
+
yield PositiveValue(template)
|
|
307
361
|
seen.add(len(template))
|
|
308
362
|
|
|
309
363
|
# Boundary and near-boundary sizes
|
|
@@ -315,99 +369,99 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
|
315
369
|
# One item more than minimum if possible
|
|
316
370
|
larger = min_items + 1
|
|
317
371
|
if larger not in seen and (max_items is None or larger <= max_items):
|
|
318
|
-
yield ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger})
|
|
372
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger}))
|
|
319
373
|
seen.add(larger)
|
|
320
374
|
|
|
321
375
|
if max_items is not None:
|
|
322
|
-
if max_items not in seen:
|
|
323
|
-
yield ctx.generate_from_schema({**schema, "minItems": max_items})
|
|
376
|
+
if max_items < BUFFER_SIZE and max_items not in seen:
|
|
377
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": max_items}))
|
|
324
378
|
seen.add(max_items)
|
|
325
379
|
|
|
326
380
|
# One item smaller than maximum if possible
|
|
327
381
|
smaller = max_items - 1
|
|
328
|
-
if
|
|
329
|
-
|
|
382
|
+
if (
|
|
383
|
+
smaller < BUFFER_SIZE
|
|
384
|
+
and smaller > 0
|
|
385
|
+
and smaller not in seen
|
|
386
|
+
and (min_items is None or smaller >= min_items)
|
|
387
|
+
):
|
|
388
|
+
yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller}))
|
|
330
389
|
seen.add(smaller)
|
|
331
390
|
|
|
332
391
|
|
|
333
|
-
def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator:
|
|
334
|
-
yield template
|
|
392
|
+
def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator[GeneratedValue, None, None]:
|
|
393
|
+
yield PositiveValue(template)
|
|
335
394
|
# Only required properties
|
|
336
395
|
properties = schema.get("properties", {})
|
|
337
396
|
if set(properties) != set(schema.get("required", {})):
|
|
338
397
|
only_required = {k: v for k, v in template.items() if k in schema.get("required", [])}
|
|
339
|
-
yield only_required
|
|
398
|
+
yield PositiveValue(only_required)
|
|
340
399
|
seen = set()
|
|
341
400
|
for name, sub_schema in properties.items():
|
|
342
401
|
seen.add(_to_hashable_key(template.get(name)))
|
|
343
402
|
for new in cover_schema_iter(ctx, sub_schema):
|
|
344
|
-
key = _to_hashable_key(new)
|
|
403
|
+
key = _to_hashable_key(new.value)
|
|
345
404
|
if key not in seen:
|
|
346
|
-
yield {**template, name: new}
|
|
405
|
+
yield PositiveValue({**template, name: new.value})
|
|
347
406
|
seen.add(key)
|
|
348
407
|
seen.clear()
|
|
349
408
|
|
|
350
409
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
return JSON_STRATEGY.filter(lambda x: x not in value)
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def _negative_enum(ctx: CoverageContext, value: list) -> Generator:
|
|
357
|
-
try:
|
|
358
|
-
strategy = _get_negative_enum_strategy(tuple(value))
|
|
359
|
-
except TypeError:
|
|
360
|
-
# The value is not hashable
|
|
361
|
-
strategy = JSON_STRATEGY.filter(lambda x: x not in value)
|
|
410
|
+
def _negative_enum(ctx: CoverageContext, value: list) -> Generator[GeneratedValue, None, None]:
|
|
411
|
+
strategy = JSON_STRATEGY.filter(lambda x: x not in value)
|
|
362
412
|
# The exact negative value is not important here
|
|
363
|
-
yield ctx.generate_from(strategy, cached=True)
|
|
413
|
+
yield NegativeValue(ctx.generate_from(strategy, cached=True))
|
|
364
414
|
|
|
365
415
|
|
|
366
|
-
def _negative_properties(
|
|
416
|
+
def _negative_properties(
|
|
417
|
+
ctx: CoverageContext, template: dict, properties: dict
|
|
418
|
+
) -> Generator[GeneratedValue, None, None]:
|
|
367
419
|
nctx = ctx.with_negative()
|
|
368
420
|
for key, sub_schema in properties.items():
|
|
369
421
|
for value in cover_schema_iter(nctx, sub_schema):
|
|
370
|
-
yield {**template, key: value}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
@lru_cache(maxsize=128)
|
|
374
|
-
def _get_negative_pattern_strategy(value: str) -> st.SearchStrategy:
|
|
375
|
-
return st.text().filter(lambda x: x != value)
|
|
422
|
+
yield NegativeValue({**template, key: value.value})
|
|
376
423
|
|
|
377
424
|
|
|
378
|
-
def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator:
|
|
379
|
-
yield ctx.generate_from(
|
|
425
|
+
def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator[GeneratedValue, None, None]:
|
|
426
|
+
yield NegativeValue(ctx.generate_from(st.text().filter(lambda x: x != pattern), cached=True))
|
|
380
427
|
|
|
381
428
|
|
|
382
429
|
def _with_negated_key(schema: dict, key: str, value: Any) -> dict:
|
|
383
430
|
return {"allOf": [{k: v for k, v in schema.items() if k != key}, {"not": {key: value}}]}
|
|
384
431
|
|
|
385
432
|
|
|
386
|
-
def _negative_multiple_of(
|
|
387
|
-
|
|
433
|
+
def _negative_multiple_of(
|
|
434
|
+
ctx: CoverageContext, schema: dict, multiple_of: int | float
|
|
435
|
+
) -> Generator[GeneratedValue, None, None]:
|
|
436
|
+
yield NegativeValue(ctx.generate_from_schema(_with_negated_key(schema, "multipleOf", multiple_of)))
|
|
388
437
|
|
|
389
438
|
|
|
390
|
-
def _negative_unique_items(ctx: CoverageContext, schema: dict) -> Generator:
|
|
439
|
+
def _negative_unique_items(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
|
|
391
440
|
unique = ctx.generate_from_schema({**schema, "type": "array", "minItems": 1, "maxItems": 1})
|
|
392
|
-
yield unique + unique
|
|
441
|
+
yield NegativeValue(unique + unique)
|
|
393
442
|
|
|
394
443
|
|
|
395
|
-
def _negative_required(
|
|
444
|
+
def _negative_required(
|
|
445
|
+
ctx: CoverageContext, template: dict, required: list[str]
|
|
446
|
+
) -> Generator[GeneratedValue, None, None]:
|
|
396
447
|
for key in required:
|
|
397
|
-
yield {k: v for k, v in template.items() if k != key}
|
|
448
|
+
yield NegativeValue({k: v for k, v in template.items() if k != key})
|
|
398
449
|
|
|
399
450
|
|
|
400
|
-
def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generator:
|
|
451
|
+
def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generator[GeneratedValue, None, None]:
|
|
401
452
|
# Hypothesis-jsonschema does not canonicalise it properly right now, which leads to unsatisfiable schema
|
|
402
453
|
without_format = {k: v for k, v in schema.items() if k != "format"}
|
|
403
454
|
without_format.setdefault("type", "string")
|
|
404
455
|
strategy = from_schema(without_format)
|
|
405
456
|
if format in jsonschema.Draft202012Validator.FORMAT_CHECKER.checkers:
|
|
406
|
-
strategy = strategy.filter(
|
|
407
|
-
|
|
457
|
+
strategy = strategy.filter(
|
|
458
|
+
lambda v: (format == "hostname" and v == "")
|
|
459
|
+
or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
|
|
460
|
+
)
|
|
461
|
+
yield NegativeValue(ctx.generate_from(strategy))
|
|
408
462
|
|
|
409
463
|
|
|
410
|
-
def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Generator:
|
|
464
|
+
def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Generator[GeneratedValue, None, None]:
|
|
411
465
|
strategies = {
|
|
412
466
|
"integer": st.integers(),
|
|
413
467
|
"number": NUMERIC_STRATEGY,
|
|
@@ -429,5 +483,5 @@ def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Gene
|
|
|
429
483
|
strategies["number"] = FLOAT_STRATEGY.filter(lambda x: x != int(x))
|
|
430
484
|
negative_strategy = combine_strategies(tuple(strategies.values())).filter(lambda x: _to_hashable_key(x) not in seen)
|
|
431
485
|
value = ctx.generate_from(negative_strategy, cached=True)
|
|
432
|
-
yield value
|
|
486
|
+
yield NegativeValue(value)
|
|
433
487
|
seen.add(_to_hashable_key(value))
|
schemathesis/models.py
CHANGED
|
@@ -125,6 +125,15 @@ def prepare_request_data(kwargs: dict[str, Any]) -> PreparedRequestData:
|
|
|
125
125
|
)
|
|
126
126
|
|
|
127
127
|
|
|
128
|
+
@dataclass
|
|
129
|
+
class TestPhase(Enum):
|
|
130
|
+
__test__ = False
|
|
131
|
+
|
|
132
|
+
EXPLICIT = "explicit"
|
|
133
|
+
COVERAGE = "coverage"
|
|
134
|
+
GENERATE = "generate"
|
|
135
|
+
|
|
136
|
+
|
|
128
137
|
@dataclass
|
|
129
138
|
class GenerationMetadata:
|
|
130
139
|
"""Stores various information about how data is generated."""
|
|
@@ -134,8 +143,9 @@ class GenerationMetadata:
|
|
|
134
143
|
headers: DataGenerationMethod | None
|
|
135
144
|
cookies: DataGenerationMethod | None
|
|
136
145
|
body: DataGenerationMethod | None
|
|
146
|
+
phase: TestPhase
|
|
137
147
|
|
|
138
|
-
__slots__ = ("query", "path_parameters", "headers", "cookies", "body")
|
|
148
|
+
__slots__ = ("query", "path_parameters", "headers", "cookies", "body", "phase")
|
|
139
149
|
|
|
140
150
|
|
|
141
151
|
@dataclass(repr=False)
|
|
@@ -968,6 +978,7 @@ class Interaction:
|
|
|
968
978
|
checks: list[Check]
|
|
969
979
|
status: Status
|
|
970
980
|
data_generation_method: DataGenerationMethod
|
|
981
|
+
phase: TestPhase | None
|
|
971
982
|
recorded_at: str = field(default_factory=lambda: datetime.datetime.now(TIMEZONE).isoformat())
|
|
972
983
|
|
|
973
984
|
@classmethod
|
|
@@ -978,6 +989,7 @@ class Interaction:
|
|
|
978
989
|
status=status,
|
|
979
990
|
checks=checks,
|
|
980
991
|
data_generation_method=cast(DataGenerationMethod, case.data_generation_method),
|
|
992
|
+
phase=case.meta.phase if case.meta is not None else None,
|
|
981
993
|
)
|
|
982
994
|
|
|
983
995
|
@classmethod
|
|
@@ -1000,6 +1012,7 @@ class Interaction:
|
|
|
1000
1012
|
status=status,
|
|
1001
1013
|
checks=checks,
|
|
1002
1014
|
data_generation_method=cast(DataGenerationMethod, case.data_generation_method),
|
|
1015
|
+
phase=case.meta.phase if case.meta is not None else None,
|
|
1003
1016
|
)
|
|
1004
1017
|
|
|
1005
1018
|
|
|
@@ -28,7 +28,7 @@ from ..exceptions import (
|
|
|
28
28
|
make_unique_by_key,
|
|
29
29
|
)
|
|
30
30
|
from ..generation import DataGenerationMethod
|
|
31
|
-
from ..models import Case, Check, Interaction, Request, Response, Status, TestResult
|
|
31
|
+
from ..models import Case, Check, Interaction, Request, Response, Status, TestPhase, TestResult
|
|
32
32
|
from ..transports import deserialize_payload, serialize_payload
|
|
33
33
|
|
|
34
34
|
if TYPE_CHECKING:
|
|
@@ -385,6 +385,7 @@ class SerializedInteraction:
|
|
|
385
385
|
checks: list[SerializedCheck]
|
|
386
386
|
status: Status
|
|
387
387
|
data_generation_method: DataGenerationMethod
|
|
388
|
+
phase: TestPhase | None
|
|
388
389
|
recorded_at: str
|
|
389
390
|
|
|
390
391
|
@classmethod
|
|
@@ -395,6 +396,7 @@ class SerializedInteraction:
|
|
|
395
396
|
checks=[SerializedCheck.from_check(check) for check in interaction.checks],
|
|
396
397
|
status=interaction.status,
|
|
397
398
|
data_generation_method=interaction.data_generation_method,
|
|
399
|
+
phase=interaction.phase,
|
|
398
400
|
recorded_at=interaction.recorded_at,
|
|
399
401
|
)
|
|
400
402
|
|
|
@@ -25,7 +25,7 @@ from ...generation import DataGenerationMethod, GenerationConfig
|
|
|
25
25
|
from ...hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
|
|
26
26
|
from ...internal.copy import fast_deepcopy
|
|
27
27
|
from ...internal.validation import is_illegal_surrogate
|
|
28
|
-
from ...models import APIOperation, Case, GenerationMetadata, cant_serialize
|
|
28
|
+
from ...models import APIOperation, Case, GenerationMetadata, TestPhase, cant_serialize
|
|
29
29
|
from ...serializers import Binary
|
|
30
30
|
from ...transports.content_types import parse_content_type
|
|
31
31
|
from ...transports.headers import has_invalid_characters, is_latin_1_encodable
|
|
@@ -123,6 +123,7 @@ def get_case_strategy(
|
|
|
123
123
|
body: Any = NOT_SET,
|
|
124
124
|
media_type: str | None = None,
|
|
125
125
|
skip_on_not_negated: bool = True,
|
|
126
|
+
phase: TestPhase = TestPhase.GENERATE,
|
|
126
127
|
) -> Any:
|
|
127
128
|
"""A strategy that creates `Case` instances.
|
|
128
129
|
|
|
@@ -213,6 +214,7 @@ def get_case_strategy(
|
|
|
213
214
|
headers=headers_.generator,
|
|
214
215
|
cookies=cookies_.generator,
|
|
215
216
|
body=body_.generator,
|
|
217
|
+
phase=phase,
|
|
216
218
|
),
|
|
217
219
|
)
|
|
218
220
|
auth_context = auths.AuthContext(
|
|
@@ -13,7 +13,7 @@ from hypothesis_jsonschema import from_schema
|
|
|
13
13
|
from ...constants import DEFAULT_RESPONSE_TIMEOUT
|
|
14
14
|
from ...generation import get_single_example
|
|
15
15
|
from ...internal.copy import fast_deepcopy
|
|
16
|
-
from ...models import APIOperation, Case
|
|
16
|
+
from ...models import APIOperation, Case, TestPhase
|
|
17
17
|
from ._hypothesis import get_case_strategy, get_default_format_strategies
|
|
18
18
|
from .constants import LOCATION_TO_CONTAINER
|
|
19
19
|
from .formats import STRING_FORMATS
|
|
@@ -67,6 +67,8 @@ def get_strategies_from_examples(
|
|
|
67
67
|
examples = list(extract_top_level(operation))
|
|
68
68
|
# Add examples from parameter's schemas
|
|
69
69
|
examples.extend(extract_from_schemas(operation))
|
|
70
|
+
as_strategy_kwargs = as_strategy_kwargs or {}
|
|
71
|
+
as_strategy_kwargs["phase"] = TestPhase.EXPLICIT
|
|
70
72
|
return [
|
|
71
73
|
get_case_strategy(operation=operation, **{**parameters, **(as_strategy_kwargs or {})}).map(serialize_components)
|
|
72
74
|
for parameters in produce_combinations(examples)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 3.35.
|
|
3
|
+
Version: 3.35.1
|
|
4
4
|
Summary: Property-based testing framework for Open API and GraphQL based apps
|
|
5
5
|
Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
|
|
6
6
|
Project-URL: Changelog, https://schemathesis.readthedocs.io/en/stable/changelog.html
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
schemathesis/__init__.py,sha256=pNaTfaC3NSdediNQuH9QAcuIx3U-MmSydvhS65FZrxw,1984
|
|
2
2
|
schemathesis/_compat.py,sha256=y4RZd59i2NCnZ91VQhnKeMn_8t3SgvLOk2Xm8nymUHY,1837
|
|
3
3
|
schemathesis/_dependency_versions.py,sha256=pjEkkGAfOQJYNb-9UOo84V8nj_lKHr_TGDVdFwY2UU0,816
|
|
4
|
-
schemathesis/_hypothesis.py,sha256=
|
|
4
|
+
schemathesis/_hypothesis.py,sha256=eRbPWW9WwFJBEsb3nkhZGSYisKfwTJscNlh6x5s5Yvo,14055
|
|
5
5
|
schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
|
|
6
6
|
schemathesis/_override.py,sha256=3CbA7P9Q89W3ymaYxiOV5Xpv1yhoBqroLK4YRpYMjX4,1630
|
|
7
7
|
schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
|
|
@@ -17,7 +17,7 @@ schemathesis/graphql.py,sha256=YkoKWY5K8lxp7H3ikAs-IsoDbiPwJvChG7O8p3DgwtI,229
|
|
|
17
17
|
schemathesis/hooks.py,sha256=Uv9rZHqM2bpb_uYBjf4kqsMeu7XdLOHpRWSaN43xIgw,14774
|
|
18
18
|
schemathesis/lazy.py,sha256=hGwSuWe5tDaGpjZTV4Mj8zqdrHDYHxR22N2p5h2yh1g,18897
|
|
19
19
|
schemathesis/loaders.py,sha256=OtCD1o0TVmSNAUF7dgHpouoAXtY6w9vEtsRVGv4lE0g,4588
|
|
20
|
-
schemathesis/models.py,sha256=
|
|
20
|
+
schemathesis/models.py,sha256=ZA-neOIY3Q6-BLvX9B3TLUO6E-coHCkqcz4S8uDoSAM,44600
|
|
21
21
|
schemathesis/parameters.py,sha256=PndmqQRlEYsCt1kWjSShPsFf6vj7X_7FRdz_-A95eNg,2258
|
|
22
22
|
schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
23
|
schemathesis/sanitization.py,sha256=_qSt04f_XcHrgguyUnowvdfj-b6u409Ubu07i0ivQUQ,9011
|
|
@@ -30,7 +30,7 @@ schemathesis/utils.py,sha256=bYvB3l1iMxiUNHu7_1qhOj5gJf_8QUssL4Uoqgrux9A,4878
|
|
|
30
30
|
schemathesis/cli/__init__.py,sha256=O2VIpE9EWFrklWlVTTFUQGShxN8Ij9nbaOJFslDEmHY,73976
|
|
31
31
|
schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
|
|
32
32
|
schemathesis/cli/callbacks.py,sha256=PJs64n6qGrGC5Yv_yl3fGm797cvN6pp2enFLmSljGKs,15127
|
|
33
|
-
schemathesis/cli/cassettes.py,sha256=
|
|
33
|
+
schemathesis/cli/cassettes.py,sha256=KyODNf0r1ieLe3A-CGcU2EEkQYr0fgVut27AOl54MOM,18615
|
|
34
34
|
schemathesis/cli/constants.py,sha256=wk-0GsoJIel8wFFerQ6Kf_6eAYUtIWkwMFwyAqv3yj4,1635
|
|
35
35
|
schemathesis/cli/context.py,sha256=6OYpSbeRobkdyDGvSj_2_zwEelM9K6fXpUCUjVC2sQM,2243
|
|
36
36
|
schemathesis/cli/debug.py,sha256=_YA-bX1ujHl4bqQDEum7M-I2XHBTEGbvgkhvcvKhmgU,658
|
|
@@ -60,7 +60,7 @@ schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZE
|
|
|
60
60
|
schemathesis/generation/__init__.py,sha256=IzldWIswXBjCUaVInvXDoaXIrUbtZIU5cisnWcg2IX8,1609
|
|
61
61
|
schemathesis/generation/_hypothesis.py,sha256=Qel0mBsZV6tOEspRGfbJKFZevaMgHJjzY1F0Oo1bP_Y,1408
|
|
62
62
|
schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
|
|
63
|
-
schemathesis/generation/coverage.py,sha256=
|
|
63
|
+
schemathesis/generation/coverage.py,sha256=A-U4qHT6y9oBjuLxDvRZd7l227zLh-c15ai14pifUxo,19642
|
|
64
64
|
schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
|
|
65
65
|
schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
|
|
66
66
|
schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
|
|
@@ -74,7 +74,7 @@ schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QA
|
|
|
74
74
|
schemathesis/runner/__init__.py,sha256=-aedUaRBCiTiaooC0OsBbdi4XP8APFOpj6eOzwt5nQ8,21366
|
|
75
75
|
schemathesis/runner/events.py,sha256=F3TizErDI490cDakpyeCbyK9IOecWpuToQ1HGXvBXzo,11531
|
|
76
76
|
schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
|
|
77
|
-
schemathesis/runner/serialization.py,sha256=
|
|
77
|
+
schemathesis/runner/serialization.py,sha256=C8F_2KiszQx16DkWm37viN_AJqLi-MF3YyvkdaFSmCs,20116
|
|
78
78
|
schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
|
|
79
79
|
schemathesis/runner/impl/core.py,sha256=H4sYY59kMXQPeOD4BH3j47Ymvix71FSgyQh36flrQxM,45605
|
|
80
80
|
schemathesis/runner/impl/solo.py,sha256=N7-pUL6nWGiSRUC4Zqy1T4h99vbeQowP6b6cMnobOow,3042
|
|
@@ -102,12 +102,12 @@ schemathesis/specs/graphql/schemas.py,sha256=L7u73YXnmqypghWhmj5FaGUAmU57IporT9N
|
|
|
102
102
|
schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzMSpnVoRWvxy0,1635
|
|
103
103
|
schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
|
|
104
104
|
schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
|
|
105
|
-
schemathesis/specs/openapi/_hypothesis.py,sha256=
|
|
105
|
+
schemathesis/specs/openapi/_hypothesis.py,sha256=XgKq36ONJIWM-8ASnDpzOgcCcVz-uUQw74bOxcUC3n8,24201
|
|
106
106
|
schemathesis/specs/openapi/checks.py,sha256=LiwoL5W_qK40j-JFSc9hfM8IGSszMBUWe71YZJ5FBzw,19931
|
|
107
107
|
schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
|
|
108
108
|
schemathesis/specs/openapi/converter.py,sha256=TaYgc5BBHPdkN-n0lqpbeVgLu3eL3L8Wu3y_Vo3TJaQ,2800
|
|
109
109
|
schemathesis/specs/openapi/definitions.py,sha256=Z186F0gNBSCmPg-Kk7Q-n6XxEZHIOzgUyeqixlC62XE,94058
|
|
110
|
-
schemathesis/specs/openapi/examples.py,sha256=
|
|
110
|
+
schemathesis/specs/openapi/examples.py,sha256=_6vqwVfGuPaJ9GTqmlk6siiS0pikk5wxelLsFQJlaEc,16283
|
|
111
111
|
schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
|
|
112
112
|
schemathesis/specs/openapi/links.py,sha256=DCOu14VOFqKYYFbQJHWICDpmTBzJfeP2v2FXBwW3vBI,17531
|
|
113
113
|
schemathesis/specs/openapi/loaders.py,sha256=AcpvTK8qdirSRcHcinCjQbwfSQSx448LAh_GvFML1C0,25515
|
|
@@ -147,8 +147,8 @@ schemathesis/transports/auth.py,sha256=yELjkEkfx4g74hNrd0Db9aFf0xDJDRIwhg2vzKOTZ
|
|
|
147
147
|
schemathesis/transports/content_types.py,sha256=VrcRQvF5T_TUjrCyrZcYF2LOwKfs3IrLcMtkVSp1ImI,2189
|
|
148
148
|
schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
|
|
149
149
|
schemathesis/transports/responses.py,sha256=6-gvVcRK0Ho_lSydUysBNFWoJwZEiEgf6Iv-GWkQGd8,1675
|
|
150
|
-
schemathesis-3.35.
|
|
151
|
-
schemathesis-3.35.
|
|
152
|
-
schemathesis-3.35.
|
|
153
|
-
schemathesis-3.35.
|
|
154
|
-
schemathesis-3.35.
|
|
150
|
+
schemathesis-3.35.1.dist-info/METADATA,sha256=LSbI2JYysmP_4r_z_-lan3zPZW1ymjSoooGoQ81W0R0,19287
|
|
151
|
+
schemathesis-3.35.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
152
|
+
schemathesis-3.35.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
|
|
153
|
+
schemathesis-3.35.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
|
|
154
|
+
schemathesis-3.35.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|