schemathesis 4.3.6__py3-none-any.whl → 4.3.8__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.
Potentially problematic release.
This version of schemathesis might be problematic. Click here for more details.
- schemathesis/config/_phases.py +1 -1
- schemathesis/core/jsonschema/bundler.py +17 -8
- schemathesis/generation/case.py +5 -1
- schemathesis/specs/openapi/adapter/parameters.py +37 -11
- schemathesis/specs/openapi/adapter/responses.py +1 -1
- schemathesis/specs/openapi/checks.py +20 -2
- schemathesis/specs/openapi/schemas.py +3 -1
- schemathesis/specs/openapi/stateful/dependencies/inputs.py +35 -8
- schemathesis/specs/openapi/stateful/dependencies/models.py +37 -22
- schemathesis/specs/openapi/stateful/dependencies/resources.py +26 -0
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +1 -1
- {schemathesis-4.3.6.dist-info → schemathesis-4.3.8.dist-info}/METADATA +1 -1
- {schemathesis-4.3.6.dist-info → schemathesis-4.3.8.dist-info}/RECORD +16 -16
- {schemathesis-4.3.6.dist-info → schemathesis-4.3.8.dist-info}/WHEEL +0 -0
- {schemathesis-4.3.6.dist-info → schemathesis-4.3.8.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.3.6.dist-info → schemathesis-4.3.8.dist-info}/licenses/LICENSE +0 -0
schemathesis/config/_phases.py
CHANGED
|
@@ -102,7 +102,7 @@ class CoveragePhaseConfig(DiffBase):
|
|
|
102
102
|
) -> None:
|
|
103
103
|
self.enabled = enabled
|
|
104
104
|
self.generate_duplicate_query_parameters = generate_duplicate_query_parameters
|
|
105
|
-
self.unexpected_methods = unexpected_methods
|
|
105
|
+
self.unexpected_methods = unexpected_methods if unexpected_methods is not None else DEFAULT_UNEXPECTED_METHODS
|
|
106
106
|
self.generation = generation or GenerationConfig()
|
|
107
107
|
self.checks = checks or ChecksConfig()
|
|
108
108
|
self._is_default = (
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from dataclasses import dataclass
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
from schemathesis.core.errors import InfiniteRecursiveReference
|
|
@@ -24,6 +25,14 @@ class BundleError(Exception):
|
|
|
24
25
|
return f"Cannot bundle `{self.reference}`: expected JSON Schema (object or boolean), got {to_json_type_name(self.value)}"
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
@dataclass
|
|
29
|
+
class Bundle:
|
|
30
|
+
schema: JsonSchema
|
|
31
|
+
name_to_uri: dict[str, str]
|
|
32
|
+
|
|
33
|
+
__slots__ = ("schema", "name_to_uri")
|
|
34
|
+
|
|
35
|
+
|
|
27
36
|
class Bundler:
|
|
28
37
|
"""Bundler tracks schema ids stored in a bundle."""
|
|
29
38
|
|
|
@@ -34,16 +43,16 @@ class Bundler:
|
|
|
34
43
|
def __init__(self) -> None:
|
|
35
44
|
self.counter = 0
|
|
36
45
|
|
|
37
|
-
def bundle(self, schema: JsonSchema, resolver: RefResolver, *, inline_recursive: bool) ->
|
|
46
|
+
def bundle(self, schema: JsonSchema, resolver: RefResolver, *, inline_recursive: bool) -> Bundle:
|
|
38
47
|
"""Bundle a JSON Schema by embedding all references."""
|
|
39
48
|
# Inlining recursive reference is required (for now) for data generation, but is unsound for data validation
|
|
40
49
|
if not isinstance(schema, dict):
|
|
41
|
-
return schema
|
|
50
|
+
return Bundle(schema=schema, name_to_uri={})
|
|
42
51
|
|
|
43
52
|
# Track visited URIs and their local definition names
|
|
44
53
|
inlining_for_recursion: set[str] = set()
|
|
45
54
|
visited: set[str] = set()
|
|
46
|
-
|
|
55
|
+
uri_to_name: dict[str, str] = {}
|
|
47
56
|
defs = {}
|
|
48
57
|
|
|
49
58
|
has_recursive_references = False
|
|
@@ -52,11 +61,11 @@ class Bundler:
|
|
|
52
61
|
|
|
53
62
|
def get_def_name(uri: str) -> str:
|
|
54
63
|
"""Generate or retrieve the local definition name for a URI."""
|
|
55
|
-
name =
|
|
64
|
+
name = uri_to_name.get(uri)
|
|
56
65
|
if name is None:
|
|
57
66
|
self.counter += 1
|
|
58
67
|
name = f"schema{self.counter}"
|
|
59
|
-
|
|
68
|
+
uri_to_name[uri] = name
|
|
60
69
|
return name
|
|
61
70
|
|
|
62
71
|
def bundle_recursive(current: JsonSchema | list[JsonSchema]) -> JsonSchema | list[JsonSchema]:
|
|
@@ -162,13 +171,13 @@ class Bundler:
|
|
|
162
171
|
for value in defs.values():
|
|
163
172
|
if isinstance(value, dict):
|
|
164
173
|
result.update(value)
|
|
165
|
-
return result
|
|
174
|
+
return Bundle(schema=result, name_to_uri={})
|
|
166
175
|
|
|
167
176
|
if defs:
|
|
168
177
|
bundled[BUNDLE_STORAGE_KEY] = defs
|
|
169
|
-
return bundled
|
|
178
|
+
return Bundle(schema=bundled, name_to_uri={v: k for k, v in uri_to_name.items()})
|
|
170
179
|
|
|
171
180
|
|
|
172
|
-
def bundle(schema: JsonSchema, resolver: RefResolver, *, inline_recursive: bool) ->
|
|
181
|
+
def bundle(schema: JsonSchema, resolver: RefResolver, *, inline_recursive: bool) -> Bundle:
|
|
173
182
|
"""Bundle a JSON Schema by embedding all references."""
|
|
174
183
|
return Bundler().bundle(schema, resolver, inline_recursive=inline_recursive)
|
schemathesis/generation/case.py
CHANGED
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from typing import TYPE_CHECKING, Any, Mapping
|
|
5
5
|
|
|
6
6
|
from schemathesis import transport
|
|
7
|
-
from schemathesis.checks import CHECKS, CheckContext, CheckFunction, run_checks
|
|
7
|
+
from schemathesis.checks import CHECKS, CheckContext, CheckFunction, load_all_checks, run_checks
|
|
8
8
|
from schemathesis.core import NOT_SET, SCHEMATHESIS_TEST_CASE_HEADER, NotSet, curl
|
|
9
9
|
from schemathesis.core.failures import FailureGroup, failure_report_title, format_failures
|
|
10
10
|
from schemathesis.core.transport import Response
|
|
@@ -251,6 +251,10 @@ class Case:
|
|
|
251
251
|
__tracebackhide__ = True
|
|
252
252
|
from requests.structures import CaseInsensitiveDict
|
|
253
253
|
|
|
254
|
+
# In some cases checks may not be loaded.
|
|
255
|
+
# For example - non-Schemathesis tests that manually construct `Case` instances
|
|
256
|
+
load_all_checks()
|
|
257
|
+
|
|
254
258
|
response = Response.from_any(response)
|
|
255
259
|
|
|
256
260
|
config = self.operation.schema.config.checks_config_for(
|
|
@@ -38,11 +38,13 @@ FORM_MEDIA_TYPES = frozenset(["multipart/form-data", "application/x-www-form-url
|
|
|
38
38
|
class OpenApiComponent(ABC):
|
|
39
39
|
definition: Mapping[str, Any]
|
|
40
40
|
is_required: bool
|
|
41
|
+
name_to_uri: dict[str, str]
|
|
41
42
|
adapter: SpecificationAdapter
|
|
42
43
|
|
|
43
44
|
__slots__ = (
|
|
44
45
|
"definition",
|
|
45
46
|
"is_required",
|
|
47
|
+
"name_to_uri",
|
|
46
48
|
"adapter",
|
|
47
49
|
"_optimized_schema",
|
|
48
50
|
"_unoptimized_schema",
|
|
@@ -142,9 +144,11 @@ class OpenApiParameter(OpenApiComponent):
|
|
|
142
144
|
"""OpenAPI operation parameter."""
|
|
143
145
|
|
|
144
146
|
@classmethod
|
|
145
|
-
def from_definition(
|
|
147
|
+
def from_definition(
|
|
148
|
+
cls, *, definition: Mapping[str, Any], name_to_uri: dict[str, str], adapter: SpecificationAdapter
|
|
149
|
+
) -> OpenApiParameter:
|
|
146
150
|
is_required = definition.get("required", False)
|
|
147
|
-
return cls(definition=definition, is_required=is_required, adapter=adapter)
|
|
151
|
+
return cls(definition=definition, is_required=is_required, name_to_uri=name_to_uri, adapter=adapter)
|
|
148
152
|
|
|
149
153
|
@property
|
|
150
154
|
def name(self) -> str:
|
|
@@ -174,12 +178,14 @@ class OpenApiBody(OpenApiComponent):
|
|
|
174
178
|
|
|
175
179
|
media_type: str
|
|
176
180
|
resource_name: str | None
|
|
181
|
+
name_to_uri: dict[str, str]
|
|
177
182
|
|
|
178
183
|
__slots__ = (
|
|
179
184
|
"definition",
|
|
180
185
|
"is_required",
|
|
181
186
|
"media_type",
|
|
182
187
|
"resource_name",
|
|
188
|
+
"name_to_uri",
|
|
183
189
|
"adapter",
|
|
184
190
|
"_optimized_schema",
|
|
185
191
|
"_unoptimized_schema",
|
|
@@ -195,6 +201,7 @@ class OpenApiBody(OpenApiComponent):
|
|
|
195
201
|
is_required: bool,
|
|
196
202
|
media_type: str,
|
|
197
203
|
resource_name: str | None,
|
|
204
|
+
name_to_uri: dict[str, str],
|
|
198
205
|
adapter: SpecificationAdapter,
|
|
199
206
|
) -> OpenApiBody:
|
|
200
207
|
return cls(
|
|
@@ -202,6 +209,7 @@ class OpenApiBody(OpenApiComponent):
|
|
|
202
209
|
is_required=is_required,
|
|
203
210
|
media_type=media_type,
|
|
204
211
|
resource_name=resource_name,
|
|
212
|
+
name_to_uri=name_to_uri,
|
|
205
213
|
adapter=adapter,
|
|
206
214
|
)
|
|
207
215
|
|
|
@@ -211,6 +219,7 @@ class OpenApiBody(OpenApiComponent):
|
|
|
211
219
|
*,
|
|
212
220
|
definition: Mapping[str, Any],
|
|
213
221
|
media_type: str,
|
|
222
|
+
name_to_uri: dict[str, str],
|
|
214
223
|
adapter: SpecificationAdapter,
|
|
215
224
|
) -> OpenApiBody:
|
|
216
225
|
return cls(
|
|
@@ -218,6 +227,7 @@ class OpenApiBody(OpenApiComponent):
|
|
|
218
227
|
is_required=True,
|
|
219
228
|
media_type=media_type,
|
|
220
229
|
resource_name=None,
|
|
230
|
+
name_to_uri=name_to_uri,
|
|
221
231
|
adapter=adapter,
|
|
222
232
|
)
|
|
223
233
|
|
|
@@ -273,19 +283,24 @@ def extract_parameter_schema_v3(parameter: Mapping[str, Any]) -> JsonSchema:
|
|
|
273
283
|
return media_type_object.get("schema", {})
|
|
274
284
|
|
|
275
285
|
|
|
276
|
-
def _bundle_parameter(
|
|
286
|
+
def _bundle_parameter(
|
|
287
|
+
parameter: Mapping, resolver: RefResolver, bundler: Bundler
|
|
288
|
+
) -> tuple[dict[str, Any], dict[str, str]]:
|
|
277
289
|
"""Bundle a parameter definition to make it self-contained."""
|
|
278
290
|
_, definition = maybe_resolve(parameter, resolver, "")
|
|
279
291
|
schema = definition.get("schema")
|
|
292
|
+
name_to_uri = {}
|
|
280
293
|
if schema is not None:
|
|
281
294
|
definition = {k: v for k, v in definition.items() if k != "schema"}
|
|
282
295
|
try:
|
|
283
|
-
|
|
296
|
+
bundled = bundler.bundle(schema, resolver, inline_recursive=True)
|
|
297
|
+
definition["schema"] = bundled.schema
|
|
298
|
+
name_to_uri = bundled.name_to_uri
|
|
284
299
|
except BundleError as exc:
|
|
285
300
|
location = parameter.get("in", "")
|
|
286
301
|
name = parameter.get("name", "<UNKNOWN>")
|
|
287
302
|
raise InvalidSchema.from_bundle_error(exc, location, name) from exc
|
|
288
|
-
return cast(dict, definition)
|
|
303
|
+
return cast(dict, definition), name_to_uri
|
|
289
304
|
|
|
290
305
|
|
|
291
306
|
OPENAPI_20_DEFAULT_BODY_MEDIA_TYPE = "application/json"
|
|
@@ -308,15 +323,17 @@ def iter_parameters_v2(
|
|
|
308
323
|
form_data_media_types = media_types or (OPENAPI_20_DEFAULT_FORM_MEDIA_TYPE,)
|
|
309
324
|
|
|
310
325
|
form_parameters = []
|
|
326
|
+
form_name_to_uri = {}
|
|
311
327
|
bundler = Bundler()
|
|
312
328
|
for parameter in chain(definition.get("parameters", []), shared_parameters):
|
|
313
|
-
parameter = _bundle_parameter(parameter, resolver, bundler)
|
|
329
|
+
parameter, name_to_uri = _bundle_parameter(parameter, resolver, bundler)
|
|
314
330
|
if parameter["in"] in HEADER_LOCATIONS:
|
|
315
331
|
check_header_name(parameter["name"])
|
|
316
332
|
|
|
317
333
|
if parameter["in"] == "formData":
|
|
318
334
|
# We need to gather form parameters first before creating a composite parameter for them
|
|
319
335
|
form_parameters.append(parameter)
|
|
336
|
+
form_name_to_uri.update(name_to_uri)
|
|
320
337
|
elif parameter["in"] == ParameterLocation.BODY:
|
|
321
338
|
# Take the original definition & extract the resource_name from there
|
|
322
339
|
resource_name = None
|
|
@@ -330,17 +347,20 @@ def iter_parameters_v2(
|
|
|
330
347
|
definition=parameter,
|
|
331
348
|
is_required=parameter.get("required", False),
|
|
332
349
|
media_type=media_type,
|
|
350
|
+
name_to_uri=name_to_uri,
|
|
333
351
|
resource_name=resource_name,
|
|
334
352
|
adapter=adapter,
|
|
335
353
|
)
|
|
336
354
|
else:
|
|
337
|
-
yield OpenApiParameter.from_definition(definition=parameter, adapter=adapter)
|
|
355
|
+
yield OpenApiParameter.from_definition(definition=parameter, name_to_uri=name_to_uri, adapter=adapter)
|
|
338
356
|
|
|
339
357
|
if form_parameters:
|
|
340
358
|
form_data = form_data_to_json_schema(form_parameters)
|
|
341
359
|
for media_type in form_data_media_types:
|
|
342
360
|
# Individual `formData` parameters are joined into a single "composite" one.
|
|
343
|
-
yield OpenApiBody.from_form_parameters(
|
|
361
|
+
yield OpenApiBody.from_form_parameters(
|
|
362
|
+
definition=form_data, media_type=media_type, name_to_uri=form_name_to_uri, adapter=adapter
|
|
363
|
+
)
|
|
344
364
|
|
|
345
365
|
|
|
346
366
|
def iter_parameters_v3(
|
|
@@ -356,11 +376,11 @@ def iter_parameters_v3(
|
|
|
356
376
|
|
|
357
377
|
bundler = Bundler()
|
|
358
378
|
for parameter in chain(definition.get("parameters", []), shared_parameters):
|
|
359
|
-
parameter = _bundle_parameter(parameter, resolver, bundler)
|
|
379
|
+
parameter, name_to_uri = _bundle_parameter(parameter, resolver, bundler)
|
|
360
380
|
if parameter["in"] in HEADER_LOCATIONS:
|
|
361
381
|
check_header_name(parameter["name"])
|
|
362
382
|
|
|
363
|
-
yield OpenApiParameter.from_definition(definition=parameter, adapter=adapter)
|
|
383
|
+
yield OpenApiParameter.from_definition(definition=parameter, name_to_uri=name_to_uri, adapter=adapter)
|
|
364
384
|
|
|
365
385
|
request_body_or_ref = operation.get("requestBody")
|
|
366
386
|
if request_body_or_ref is not None:
|
|
@@ -372,6 +392,7 @@ def iter_parameters_v3(
|
|
|
372
392
|
for media_type, content in request_body["content"].items():
|
|
373
393
|
resource_name = None
|
|
374
394
|
schema = content.get("schema")
|
|
395
|
+
name_to_uri = {}
|
|
375
396
|
if isinstance(schema, dict):
|
|
376
397
|
content = dict(content)
|
|
377
398
|
if "$ref" in schema:
|
|
@@ -379,7 +400,8 @@ def iter_parameters_v3(
|
|
|
379
400
|
try:
|
|
380
401
|
to_bundle = cast(dict[str, Any], schema)
|
|
381
402
|
bundled = bundler.bundle(to_bundle, resolver, inline_recursive=True)
|
|
382
|
-
content["schema"] = bundled
|
|
403
|
+
content["schema"] = bundled.schema
|
|
404
|
+
name_to_uri = bundled.name_to_uri
|
|
383
405
|
except BundleError as exc:
|
|
384
406
|
raise InvalidSchema.from_bundle_error(exc, "body") from exc
|
|
385
407
|
yield OpenApiBody.from_definition(
|
|
@@ -387,6 +409,7 @@ def iter_parameters_v3(
|
|
|
387
409
|
is_required=required,
|
|
388
410
|
media_type=media_type,
|
|
389
411
|
resource_name=resource_name,
|
|
412
|
+
name_to_uri=name_to_uri,
|
|
390
413
|
adapter=adapter,
|
|
391
414
|
)
|
|
392
415
|
|
|
@@ -400,6 +423,7 @@ def build_path_parameter_v2(kwargs: Mapping[str, Any]) -> OpenApiParameter:
|
|
|
400
423
|
|
|
401
424
|
return OpenApiParameter.from_definition(
|
|
402
425
|
definition={"in": ParameterLocation.PATH.value, "required": True, "type": "string", "minLength": 1, **kwargs},
|
|
426
|
+
name_to_uri={},
|
|
403
427
|
adapter=v2,
|
|
404
428
|
)
|
|
405
429
|
|
|
@@ -414,6 +438,7 @@ def build_path_parameter_v3_0(kwargs: Mapping[str, Any]) -> OpenApiParameter:
|
|
|
414
438
|
"schema": {"type": "string", "minLength": 1},
|
|
415
439
|
**kwargs,
|
|
416
440
|
},
|
|
441
|
+
name_to_uri={},
|
|
417
442
|
adapter=v3_0,
|
|
418
443
|
)
|
|
419
444
|
|
|
@@ -428,6 +453,7 @@ def build_path_parameter_v3_1(kwargs: Mapping[str, Any]) -> OpenApiParameter:
|
|
|
428
453
|
"schema": {"type": "string", "minLength": 1},
|
|
429
454
|
**kwargs,
|
|
430
455
|
},
|
|
456
|
+
name_to_uri={},
|
|
431
457
|
adapter=v3_1,
|
|
432
458
|
)
|
|
433
459
|
|
|
@@ -226,7 +226,7 @@ def _prepare_schema(schema: JsonSchema, resolver: RefResolver, scope: str, nulla
|
|
|
226
226
|
def _bundle_in_scope(schema: JsonSchema, resolver: RefResolver, scope: str) -> JsonSchema:
|
|
227
227
|
resolver.push_scope(scope)
|
|
228
228
|
try:
|
|
229
|
-
return bundle(schema, resolver, inline_recursive=False)
|
|
229
|
+
return bundle(schema, resolver, inline_recursive=False).schema
|
|
230
230
|
except RefResolutionError as exc:
|
|
231
231
|
raise InvalidSchema.from_reference_resolution_error(exc, None, None) from None
|
|
232
232
|
finally:
|
|
@@ -14,7 +14,7 @@ from schemathesis.core.failures import Failure
|
|
|
14
14
|
from schemathesis.core.parameters import ParameterLocation
|
|
15
15
|
from schemathesis.core.transport import Response
|
|
16
16
|
from schemathesis.generation.case import Case
|
|
17
|
-
from schemathesis.generation.meta import CoveragePhaseData
|
|
17
|
+
from schemathesis.generation.meta import CoveragePhaseData, TestPhase
|
|
18
18
|
from schemathesis.openapi.checks import (
|
|
19
19
|
AcceptedNegativeData,
|
|
20
20
|
EnsureResourceAvailability,
|
|
@@ -227,9 +227,27 @@ def negative_data_rejection(ctx: CheckContext, response: Response, case: Case) -
|
|
|
227
227
|
and response.status_code not in allowed_statuses
|
|
228
228
|
and not has_only_additional_properties_in_non_body_parameters(case)
|
|
229
229
|
):
|
|
230
|
+
extra_info = ""
|
|
231
|
+
phase = case.meta.phase
|
|
232
|
+
if phase and phase.name == TestPhase.COVERAGE and isinstance(phase.data, CoveragePhaseData):
|
|
233
|
+
parts: list[str] = []
|
|
234
|
+
if "Missing" in phase.data.description:
|
|
235
|
+
extra_info = f"\nInvalid component: {phase.data.description}"
|
|
236
|
+
else:
|
|
237
|
+
if phase.data.parameter:
|
|
238
|
+
parts.append(f"parameter `{phase.data.parameter}`")
|
|
239
|
+
location = phase.data.parameter_location
|
|
240
|
+
if location:
|
|
241
|
+
parts.append(f"in {location.name.lower()}")
|
|
242
|
+
description = phase.data.description.lower()
|
|
243
|
+
if parts:
|
|
244
|
+
parts.append(f"({description})")
|
|
245
|
+
else:
|
|
246
|
+
parts.append(f"{description}")
|
|
247
|
+
extra_info = "\nInvalid component: " + " ".join(parts)
|
|
230
248
|
raise AcceptedNegativeData(
|
|
231
249
|
operation=case.operation.label,
|
|
232
|
-
message=f"Invalid data should have been rejected\nExpected: {', '.join(config.expected_statuses)}",
|
|
250
|
+
message=f"Invalid data should have been rejected\nExpected: {', '.join(config.expected_statuses)}{extra_info}",
|
|
233
251
|
status_code=response.status_code,
|
|
234
252
|
expected_statuses=config.expected_statuses,
|
|
235
253
|
)
|
|
@@ -406,7 +406,9 @@ class BaseOpenAPISchema(BaseSchema):
|
|
|
406
406
|
and operation.get_parameter(name=param_name, location=param_location) is not None
|
|
407
407
|
):
|
|
408
408
|
continue
|
|
409
|
-
operation.add_parameter(
|
|
409
|
+
operation.add_parameter(
|
|
410
|
+
OpenApiParameter.from_definition(definition=param, name_to_uri={}, adapter=self.adapter)
|
|
411
|
+
)
|
|
410
412
|
self.dispatch_hook("before_init_operation", HookContext(operation=operation), operation)
|
|
411
413
|
return operation
|
|
412
414
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Iterator
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Iterator
|
|
4
4
|
|
|
5
5
|
from schemathesis.core import media_types
|
|
6
6
|
from schemathesis.core.errors import MalformedMediaType
|
|
7
7
|
from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY
|
|
8
8
|
from schemathesis.core.jsonschema.types import get_type
|
|
9
9
|
from schemathesis.core.parameters import ParameterLocation
|
|
10
|
+
from schemathesis.specs.openapi.adapter.parameters import resource_name_from_ref
|
|
10
11
|
from schemathesis.specs.openapi.stateful.dependencies import naming
|
|
11
12
|
from schemathesis.specs.openapi.stateful.dependencies.models import (
|
|
12
13
|
CanonicalizationCache,
|
|
@@ -179,6 +180,14 @@ GENERIC_FIELD_NAMES = frozenset(
|
|
|
179
180
|
)
|
|
180
181
|
|
|
181
182
|
|
|
183
|
+
def _maybe_resolve_bundled(root: dict[str, Any], schema: dict[str, Any]) -> dict[str, Any]:
|
|
184
|
+
# Right now, the body schema comes bundled to dependency analysis
|
|
185
|
+
if BUNDLE_STORAGE_KEY in root and "$ref" in schema:
|
|
186
|
+
key = schema["$ref"].split("/")[-1]
|
|
187
|
+
return root[BUNDLE_STORAGE_KEY][key]
|
|
188
|
+
return schema
|
|
189
|
+
|
|
190
|
+
|
|
182
191
|
def _resolve_body_dependencies(
|
|
183
192
|
*,
|
|
184
193
|
body: OpenApiBody,
|
|
@@ -190,12 +199,30 @@ def _resolve_body_dependencies(
|
|
|
190
199
|
if not isinstance(schema, dict):
|
|
191
200
|
return
|
|
192
201
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
resolved = _maybe_resolve_bundled(schema, schema)
|
|
203
|
+
|
|
204
|
+
# For `items`, we'll inject an array with extracted resource
|
|
205
|
+
items = resolved.get("items", {})
|
|
206
|
+
if items is not None:
|
|
207
|
+
resource_name = naming.from_path(operation.path)
|
|
208
|
+
|
|
209
|
+
if "$ref" in items:
|
|
210
|
+
schema_key = items["$ref"].split("/")[-1]
|
|
211
|
+
original_ref = body.name_to_uri[schema_key]
|
|
212
|
+
resource_name = resource_name_from_ref(original_ref)
|
|
213
|
+
resource = resources.get(resource_name)
|
|
214
|
+
if resource is None:
|
|
215
|
+
resource = ResourceDefinition.inferred_from_parameter(name=resource_name, parameter_name=None)
|
|
216
|
+
resources[resource_name] = resource
|
|
217
|
+
field = None
|
|
218
|
+
else:
|
|
219
|
+
field = None
|
|
220
|
+
yield InputSlot(
|
|
221
|
+
resource=resource,
|
|
222
|
+
resource_field=field,
|
|
223
|
+
parameter_name=0,
|
|
224
|
+
parameter_location=ParameterLocation.BODY,
|
|
225
|
+
)
|
|
199
226
|
|
|
200
227
|
# Inspect each property that could be a part of some other resource
|
|
201
228
|
properties = resolved.get("properties", {})
|
|
@@ -272,7 +299,7 @@ def update_input_field_bindings(resource_name: str, operations: OperationMap) ->
|
|
|
272
299
|
for operation in operations.values():
|
|
273
300
|
for input_slot in operation.inputs:
|
|
274
301
|
# Skip inputs not using this resource
|
|
275
|
-
if input_slot.resource.name != resource_name:
|
|
302
|
+
if input_slot.resource.name != resource_name or isinstance(input_slot.parameter_name, int):
|
|
276
303
|
continue
|
|
277
304
|
|
|
278
305
|
# Re-match parameter to upgraded resource fields
|
|
@@ -58,17 +58,24 @@ class DependencyGraph:
|
|
|
58
58
|
links: dict[str, LinkDefinition] = {}
|
|
59
59
|
for input_slot in consumer.inputs:
|
|
60
60
|
if input_slot.resource is output_slot.resource:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
if input_slot.resource_field is not None:
|
|
62
|
+
body_pointer = extend_pointer(
|
|
63
|
+
output_slot.pointer, input_slot.resource_field, output_slot.cardinality
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
# No resource field means use the whole resource
|
|
67
|
+
body_pointer = output_slot.pointer
|
|
64
68
|
link_name = f"{consumer.method.capitalize()}{input_slot.resource.name}"
|
|
65
69
|
parameters = {}
|
|
66
|
-
request_body = {}
|
|
70
|
+
request_body: dict[str, Any] | list = {}
|
|
67
71
|
# Data is extracted from response body
|
|
68
72
|
if input_slot.parameter_location == ParameterLocation.BODY:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
if isinstance(input_slot.parameter_name, int):
|
|
74
|
+
request_body = [f"$response.body#{body_pointer}"]
|
|
75
|
+
else:
|
|
76
|
+
request_body = {
|
|
77
|
+
input_slot.parameter_name: f"$response.body#{body_pointer}",
|
|
78
|
+
}
|
|
72
79
|
else:
|
|
73
80
|
parameters = {
|
|
74
81
|
f"{input_slot.parameter_location.value}.{input_slot.parameter_name}": f"$response.body#{body_pointer}",
|
|
@@ -76,7 +83,10 @@ class DependencyGraph:
|
|
|
76
83
|
existing = links.get(link_name)
|
|
77
84
|
if existing is not None:
|
|
78
85
|
existing.parameters.update(parameters)
|
|
79
|
-
existing.request_body
|
|
86
|
+
if isinstance(existing.request_body, dict) and isinstance(request_body, dict):
|
|
87
|
+
existing.request_body.update(request_body)
|
|
88
|
+
else:
|
|
89
|
+
existing.request_body = request_body
|
|
80
90
|
continue
|
|
81
91
|
links[link_name] = LinkDefinition(
|
|
82
92
|
operation_ref=f"#/paths/{consumer_path}/{consumer.method}",
|
|
@@ -110,7 +120,9 @@ class DependencyGraph:
|
|
|
110
120
|
continue
|
|
111
121
|
resource = self.resources[input.resource.name]
|
|
112
122
|
if (
|
|
113
|
-
input.resource_field not in resource.fields
|
|
123
|
+
input.resource_field not in resource.fields
|
|
124
|
+
and resource.name not in known_mismatches
|
|
125
|
+
and input.resource_field is not None
|
|
114
126
|
): # pragma: no cover
|
|
115
127
|
message = (
|
|
116
128
|
f"Operation '{operation.method.upper()} {operation.path}': "
|
|
@@ -120,21 +132,21 @@ class DependencyGraph:
|
|
|
120
132
|
matches = difflib.get_close_matches(input.resource_field, resource.fields, n=1, cutoff=0.6)
|
|
121
133
|
if matches:
|
|
122
134
|
message += f". Closest field - `{matches[0]}`"
|
|
123
|
-
|
|
135
|
+
if resource.fields:
|
|
124
136
|
message += f". Available fields - {', '.join(resource.fields)}"
|
|
125
137
|
else:
|
|
126
138
|
message += ". Resource has no fields"
|
|
127
139
|
raise AssertionError(message)
|
|
128
140
|
|
|
129
141
|
|
|
130
|
-
def
|
|
131
|
-
if not
|
|
132
|
-
|
|
142
|
+
def extend_pointer(base: str, field: str, cardinality: Cardinality) -> str:
|
|
143
|
+
if not base.endswith("/"):
|
|
144
|
+
base += "/"
|
|
133
145
|
if cardinality == Cardinality.MANY:
|
|
134
146
|
# For arrays, reference first element: /data → /data/0
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return
|
|
147
|
+
base += "0/"
|
|
148
|
+
base += encode_pointer(field)
|
|
149
|
+
return base
|
|
138
150
|
|
|
139
151
|
|
|
140
152
|
@dataclass
|
|
@@ -151,7 +163,7 @@ class LinkDefinition:
|
|
|
151
163
|
parameters: dict[str, str]
|
|
152
164
|
"""Parameter mappings (e.g., {'path.id': '$response.body#/id'})"""
|
|
153
165
|
|
|
154
|
-
request_body: dict[str, str]
|
|
166
|
+
request_body: dict[str, str] | list
|
|
155
167
|
"""Request body (e.g., {'path.id': '$response.body#/id'})"""
|
|
156
168
|
|
|
157
169
|
__slots__ = ("operation_ref", "parameters", "request_body")
|
|
@@ -240,10 +252,12 @@ class InputSlot:
|
|
|
240
252
|
|
|
241
253
|
# Which resource is needed
|
|
242
254
|
resource: ResourceDefinition
|
|
243
|
-
# Which field from that resource (e.g., "id")
|
|
244
|
-
|
|
255
|
+
# Which field from that resource (e.g., "id").
|
|
256
|
+
# None if passing the whole resource
|
|
257
|
+
resource_field: str | None
|
|
245
258
|
# Where it goes in the request (e.g., "userId")
|
|
246
|
-
|
|
259
|
+
# Integer means index in an array (only single items are supported)
|
|
260
|
+
parameter_name: str | int
|
|
247
261
|
parameter_location: ParameterLocation
|
|
248
262
|
|
|
249
263
|
__slots__ = ("resource", "resource_field", "parameter_name", "parameter_location")
|
|
@@ -284,8 +298,9 @@ class ResourceDefinition:
|
|
|
284
298
|
return cls(name=name, fields=[], types={}, source=DefinitionSource.SCHEMA_WITHOUT_PROPERTIES)
|
|
285
299
|
|
|
286
300
|
@classmethod
|
|
287
|
-
def inferred_from_parameter(cls, name: str, parameter_name: str) -> ResourceDefinition:
|
|
288
|
-
|
|
301
|
+
def inferred_from_parameter(cls, name: str, parameter_name: str | None) -> ResourceDefinition:
|
|
302
|
+
fields = [parameter_name] if parameter_name is not None else []
|
|
303
|
+
return cls(name=name, fields=fields, types={}, source=DefinitionSource.PARAMETER_INFERENCE)
|
|
289
304
|
|
|
290
305
|
|
|
291
306
|
class DefinitionSource(enum.IntEnum):
|
|
@@ -15,6 +15,7 @@ from schemathesis.specs.openapi.stateful.dependencies.models import (
|
|
|
15
15
|
DefinitionSource,
|
|
16
16
|
ResourceDefinition,
|
|
17
17
|
ResourceMap,
|
|
18
|
+
extend_pointer,
|
|
18
19
|
)
|
|
19
20
|
from schemathesis.specs.openapi.stateful.dependencies.naming import from_path
|
|
20
21
|
from schemathesis.specs.openapi.stateful.dependencies.schemas import (
|
|
@@ -144,6 +145,27 @@ def iter_resources_from_response(
|
|
|
144
145
|
else:
|
|
145
146
|
pointer = unwrapped.pointer
|
|
146
147
|
yield ExtractedResource(resource=resource, cardinality=cardinality, pointer=pointer)
|
|
148
|
+
# Look for sub-resources
|
|
149
|
+
properties = unwrapped.schema.get("properties")
|
|
150
|
+
if isinstance(properties, dict):
|
|
151
|
+
for field, subschema in properties.items():
|
|
152
|
+
if isinstance(subschema, dict):
|
|
153
|
+
reference = subschema.get("$ref")
|
|
154
|
+
if isinstance(reference, str):
|
|
155
|
+
result = _extract_resource_and_cardinality(
|
|
156
|
+
schema=subschema,
|
|
157
|
+
path=path,
|
|
158
|
+
resources=resources,
|
|
159
|
+
updated_resources=updated_resources,
|
|
160
|
+
resolver=resolver,
|
|
161
|
+
parent_ref=reference,
|
|
162
|
+
)
|
|
163
|
+
if result is not None:
|
|
164
|
+
subresource, cardinality = result
|
|
165
|
+
subresource_pointer = extend_pointer(pointer, field, cardinality=cardinality)
|
|
166
|
+
yield ExtractedResource(
|
|
167
|
+
resource=subresource, cardinality=cardinality, pointer=subresource_pointer
|
|
168
|
+
)
|
|
147
169
|
|
|
148
170
|
|
|
149
171
|
def _recover_ref_from_allof(*, branches: list[dict], pointer: str, resolver: RefResolver) -> str | None:
|
|
@@ -264,6 +286,10 @@ def _extract_resource_from_schema(
|
|
|
264
286
|
if resource is None or resource.source < DefinitionSource.SCHEMA_WITH_PROPERTIES:
|
|
265
287
|
_, resolved = maybe_resolve(schema, resolver, "")
|
|
266
288
|
|
|
289
|
+
if "type" in resolved and resolved["type"] != "object" and "properties" not in resolved:
|
|
290
|
+
# Skip strings, etc
|
|
291
|
+
return None
|
|
292
|
+
|
|
267
293
|
properties = resolved.get("properties")
|
|
268
294
|
if properties:
|
|
269
295
|
fields = sorted(properties)
|
|
@@ -93,7 +93,7 @@ def canonicalize(schema: dict[str, Any], resolver: RefResolver) -> Mapping[str,
|
|
|
93
93
|
|
|
94
94
|
# Canonicalisation in `hypothesis_jsonschema` requires all references to be resovable and non-recursive
|
|
95
95
|
# On the Schemathesis side bundling solves this problem
|
|
96
|
-
bundled = bundle(schema, resolver, inline_recursive=True)
|
|
96
|
+
bundled = bundle(schema, resolver, inline_recursive=True).schema
|
|
97
97
|
canonicalized = canonicalish(bundled)
|
|
98
98
|
resolved = resolve_all_refs(canonicalized)
|
|
99
99
|
resolved.pop(BUNDLE_STORAGE_KEY, None)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: schemathesis
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.8
|
|
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://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
|
|
@@ -39,7 +39,7 @@ schemathesis/config/_health_check.py,sha256=zC9inla5ibMBlEy5WyM4_TME7ju_KH3Bwfo2
|
|
|
39
39
|
schemathesis/config/_operations.py,sha256=JvfMkieYBkbEmZRb4cTvQLfvHQLhmsxa3GXzgjOtmFc,12383
|
|
40
40
|
schemathesis/config/_output.py,sha256=3G9SOi-4oNcQPHeNRG3HggFCwvcKOW1kF28a9m0H-pU,4434
|
|
41
41
|
schemathesis/config/_parameters.py,sha256=i76Hwaf834fBAMmtKfKTl1SFCicJ-Y-5tZt5QNGW2fA,618
|
|
42
|
-
schemathesis/config/_phases.py,sha256=
|
|
42
|
+
schemathesis/config/_phases.py,sha256=vezsRZAdDmtPcOjwdwAmtdb6Hkkj9gMRsdVKah71Zio,8777
|
|
43
43
|
schemathesis/config/_projects.py,sha256=MpXFBIkNAWAzE_NWQISI9R8dlbCVId2eXGdIqZBgK98,20468
|
|
44
44
|
schemathesis/config/_rate_limit.py,sha256=ekEW-jP_Ichk_O6hYpj-h2TTTKfp7Fm0nyFUbvlWcbA,456
|
|
45
45
|
schemathesis/config/_report.py,sha256=ZECDpaCY4WWHD5UbjvgZoSjLz-rlTvfd5Ivzdgzqf2I,3891
|
|
@@ -69,7 +69,7 @@ schemathesis/core/transport.py,sha256=LQcamAkFqJ0HuXQzepevAq2MCJW-uq5Nm-HE9yc7HM
|
|
|
69
69
|
schemathesis/core/validation.py,sha256=b0USkKzkWvdz3jOW1JXYc_TfYshfKZeP7xAUnMqcNoc,2303
|
|
70
70
|
schemathesis/core/version.py,sha256=dOBUWrY3-uA2NQXJp9z7EtZgkR6jYeLg8sMhQCL1mcI,205
|
|
71
71
|
schemathesis/core/jsonschema/__init__.py,sha256=gBZGsXIpK2EFfcp8x0b69dqzWAm2OeZHepKImkkLvoE,320
|
|
72
|
-
schemathesis/core/jsonschema/bundler.py,sha256=
|
|
72
|
+
schemathesis/core/jsonschema/bundler.py,sha256=a8cQMJ7EQe3eWvS3haS2ZpxOUwzA4PY4OLI0Uyx045A,8472
|
|
73
73
|
schemathesis/core/jsonschema/keywords.py,sha256=pjseXTfH9OItNs_Qq6ubkhNWQOrxTnwHmrP_jxrHeJU,631
|
|
74
74
|
schemathesis/core/jsonschema/references.py,sha256=c2Q4IKWUbwENNtkbFaqf8r3LLZu6GFE5YLnYQlg5tPg,6069
|
|
75
75
|
schemathesis/core/jsonschema/types.py,sha256=C7f9g8yKFuoxC5_0YNIh8QAyGU0-tj8pzTMfMDjjjVM,1248
|
|
@@ -92,7 +92,7 @@ schemathesis/engine/phases/unit/__init__.py,sha256=9dDcxyj887pktnE9YDIPNaR-vc7iq
|
|
|
92
92
|
schemathesis/engine/phases/unit/_executor.py,sha256=YDibV3lkC2UMHLvh1FSmnlaQ-SJS-R0MU2qEF4NBbf0,17235
|
|
93
93
|
schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
|
|
94
94
|
schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
|
|
95
|
-
schemathesis/generation/case.py,sha256=
|
|
95
|
+
schemathesis/generation/case.py,sha256=SLMw6zkzmeiZdaIij8_0tjTF70BrMlRSWREaqWii0uM,12508
|
|
96
96
|
schemathesis/generation/coverage.py,sha256=dZYX0gkHDHDenrubVQ58P3ww2xf91fEqk9s54AIokw4,59699
|
|
97
97
|
schemathesis/generation/meta.py,sha256=tXhUZBEdpQMn68uMx1SW8Vv59Uf6Wl6yzs-VB9lu_8o,2589
|
|
98
98
|
schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz5-kt4,2836
|
|
@@ -130,7 +130,7 @@ schemathesis/specs/graphql/schemas.py,sha256=GKJcnTAT1wUzzUr3r6wiTfiAdFLcgFQjYRR
|
|
|
130
130
|
schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
|
|
131
131
|
schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
|
|
132
132
|
schemathesis/specs/openapi/_hypothesis.py,sha256=O8vN-koBjzBVZfpD3pmgIt6ecU4ddAPHOxTAORd23Lo,22642
|
|
133
|
-
schemathesis/specs/openapi/checks.py,sha256=
|
|
133
|
+
schemathesis/specs/openapi/checks.py,sha256=12ks0V2F8-YKPkItgAc0ZrxsHufWWlsgj-jpj-cF40A,31578
|
|
134
134
|
schemathesis/specs/openapi/converter.py,sha256=4a6-8STT5snF7B-t6IsOIGdK5rV16oNqsdvWL7VFf2M,6472
|
|
135
135
|
schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
|
|
136
136
|
schemathesis/specs/openapi/examples.py,sha256=moFFfOfzepjlJOrqLc60BrEmJ4oRzwJ3SM03y_nJNMU,24097
|
|
@@ -138,14 +138,14 @@ schemathesis/specs/openapi/formats.py,sha256=4tYRdckauHxkJCmOhmdwDq_eOpHPaKloi89
|
|
|
138
138
|
schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKRcUOc41eEwKbo,2472
|
|
139
139
|
schemathesis/specs/openapi/patterns.py,sha256=GqPZEXMRdWENQxanWjBOalIZ2MQUjuxk21kmdiI703E,18027
|
|
140
140
|
schemathesis/specs/openapi/references.py,sha256=AW1laU23BkiRf0EEFM538vyVFLXycGUiucGVV461le0,1927
|
|
141
|
-
schemathesis/specs/openapi/schemas.py,sha256=
|
|
141
|
+
schemathesis/specs/openapi/schemas.py,sha256=uTxwggtWi5dWjuKlSDs0DthlNNfcRvSJtrND-PaTkrg,33758
|
|
142
142
|
schemathesis/specs/openapi/serialization.py,sha256=RPNdadne5wdhsGmjSvgKLRF58wpzpRx3wura8PsHM3o,12152
|
|
143
143
|
schemathesis/specs/openapi/utils.py,sha256=XkOJT8qD-6uhq-Tmwxk_xYku1Gy5F9pKL3ldNg_DRZw,522
|
|
144
144
|
schemathesis/specs/openapi/adapter/__init__.py,sha256=YEovBgLjnXd3WGPMJXq0KbSGHezkRlEv4dNRO7_evfk,249
|
|
145
|
-
schemathesis/specs/openapi/adapter/parameters.py,sha256=
|
|
145
|
+
schemathesis/specs/openapi/adapter/parameters.py,sha256=bVo7sgN5oCH2GVDXlqAMrwN0z5pb8VKf3_FOkSgTGDA,19567
|
|
146
146
|
schemathesis/specs/openapi/adapter/protocol.py,sha256=VDF6COcilHEUnmw76YBVur8bFiTFQHsNvaO9pR_i_KM,2709
|
|
147
147
|
schemathesis/specs/openapi/adapter/references.py,sha256=6M59pJy_U_sLh3Xzgu6-izWXtz3bjXnqJYSD65wRHtk,549
|
|
148
|
-
schemathesis/specs/openapi/adapter/responses.py,sha256=
|
|
148
|
+
schemathesis/specs/openapi/adapter/responses.py,sha256=UXcYb048SeS0MhydQY518IgYD0s0Q5YpLsBbdX5-5-s,13276
|
|
149
149
|
schemathesis/specs/openapi/adapter/security.py,sha256=W3cqlbs80NxF9SAavOi7BhtNGzdxHO476lYxiWN0D08,4945
|
|
150
150
|
schemathesis/specs/openapi/adapter/v2.py,sha256=2Rd1cTv7_I5QrBPLVfa2yD80NAErxV3tdeACjtEfXAA,1280
|
|
151
151
|
schemathesis/specs/openapi/adapter/v3_0.py,sha256=8bOE9WUDrvPivGs0w-S1PP2TXgWuaoTzMdg2_WWbi-E,1272
|
|
@@ -165,12 +165,12 @@ schemathesis/specs/openapi/stateful/control.py,sha256=QaXLSbwQWtai5lxvvVtQV3BLJ8
|
|
|
165
165
|
schemathesis/specs/openapi/stateful/inference.py,sha256=B99jSTDVi2yKxU7-raIb91xpacOrr0nZkEZY5Ej3eCY,9783
|
|
166
166
|
schemathesis/specs/openapi/stateful/links.py,sha256=SSA66mU50FFBz7e6sA37CfL-Vt0OY3gont72oFSvZYU,8163
|
|
167
167
|
schemathesis/specs/openapi/stateful/dependencies/__init__.py,sha256=0JM-FrY6Awv6gl-qDHaaK7pXbt_GKutBKPyIaph8apA,7842
|
|
168
|
-
schemathesis/specs/openapi/stateful/dependencies/inputs.py,sha256=
|
|
169
|
-
schemathesis/specs/openapi/stateful/dependencies/models.py,sha256=
|
|
168
|
+
schemathesis/specs/openapi/stateful/dependencies/inputs.py,sha256=PSactImp4OqsYMHUl2gB2pgvUlZCCKJRJKeaalclFzU,11511
|
|
169
|
+
schemathesis/specs/openapi/stateful/dependencies/models.py,sha256=Kl482Hwq2M8lYAdqGmf_8Yje3voSj1WLDUIujRUDWDQ,12286
|
|
170
170
|
schemathesis/specs/openapi/stateful/dependencies/naming.py,sha256=HfpkCB1GglX1BAKXer3llvPkQsk8wx0QZhZq7ANcdMM,12214
|
|
171
171
|
schemathesis/specs/openapi/stateful/dependencies/outputs.py,sha256=zvVUfQWNIuhMkKDpz5hsVGkkvkefLt1EswpJAnHajOw,1186
|
|
172
|
-
schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=
|
|
173
|
-
schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=
|
|
172
|
+
schemathesis/specs/openapi/stateful/dependencies/resources.py,sha256=p58XoADpMKFAun0Bx_rul-kiUlfA9PXjxHJ97dT2tBE,11202
|
|
173
|
+
schemathesis/specs/openapi/stateful/dependencies/schemas.py,sha256=snDmf2SWsvo9Oqk_X8xF8GB0Fbwx429UA6ZL2IokzDY,14101
|
|
174
174
|
schemathesis/specs/openapi/types/__init__.py,sha256=VPsWtLJle__Kodw_QqtQ3OuvBzBcCIKsTOrXy3eA7OU,66
|
|
175
175
|
schemathesis/specs/openapi/types/v3.py,sha256=Vondr9Amk6JKCIM6i6RGcmTUjFfPgOOqzBXqerccLpo,1468
|
|
176
176
|
schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
|
|
@@ -179,8 +179,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
|
|
|
179
179
|
schemathesis/transport/requests.py,sha256=wriRI9fprTplE_qEZLEz1TerX6GwkE3pwr6ZnU2o6vQ,10648
|
|
180
180
|
schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
|
|
181
181
|
schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
|
|
182
|
-
schemathesis-4.3.
|
|
183
|
-
schemathesis-4.3.
|
|
184
|
-
schemathesis-4.3.
|
|
185
|
-
schemathesis-4.3.
|
|
186
|
-
schemathesis-4.3.
|
|
182
|
+
schemathesis-4.3.8.dist-info/METADATA,sha256=O5oMaXmmfDDPdYv49lHtjDoSl5pxboEfWCOyeeeYgTo,8540
|
|
183
|
+
schemathesis-4.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
184
|
+
schemathesis-4.3.8.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
|
185
|
+
schemathesis-4.3.8.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
|
186
|
+
schemathesis-4.3.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|