schemathesis 3.36.2__py3-none-any.whl → 3.36.4__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.
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import json
7
+ from copy import copy
7
8
  import warnings
8
9
  from typing import TYPE_CHECKING, Any, Callable, Generator, Mapping
9
10
 
@@ -221,7 +222,13 @@ def _iter_coverage_cases(
221
222
 
222
223
  ctx = coverage.CoverageContext(data_generation_methods=data_generation_methods)
223
224
  meta = GenerationMetadata(
224
- query=None, path_parameters=None, headers=None, cookies=None, body=None, phase=TestPhase.COVERAGE
225
+ query=None,
226
+ path_parameters=None,
227
+ headers=None,
228
+ cookies=None,
229
+ body=None,
230
+ phase=TestPhase.COVERAGE,
231
+ description=None,
225
232
  )
226
233
  generators: dict[tuple[str, str], Generator[coverage.GeneratedValue, None, None]] = {}
227
234
  template: dict[str, Any] = {}
@@ -259,17 +266,19 @@ def _iter_coverage_cases(
259
266
  template["media_type"] = body.media_type
260
267
  case = operation.make_case(**{**template, "body": value.value, "media_type": body.media_type})
261
268
  case.data_generation_method = value.data_generation_method
262
- case.meta = meta
269
+ case.meta = copy(meta)
270
+ case.meta.description = value.description
263
271
  yield case
264
272
  for next_value in gen:
265
273
  case = operation.make_case(**{**template, "body": next_value.value, "media_type": body.media_type})
266
274
  case.data_generation_method = next_value.data_generation_method
267
- case.meta = meta
275
+ case.meta = copy(meta)
276
+ case.meta.description = next_value.description
268
277
  yield case
269
278
  elif DataGenerationMethod.positive in data_generation_methods:
270
279
  case = operation.make_case(**template)
271
280
  case.data_generation_method = DataGenerationMethod.positive
272
- case.meta = meta
281
+ case.meta = copy(meta)
273
282
  yield case
274
283
  for (location, name), gen in generators.items():
275
284
  container_name = LOCATION_TO_CONTAINER[location]
@@ -281,7 +290,8 @@ def _iter_coverage_cases(
281
290
  generated = value.value
282
291
  case = operation.make_case(**{**template, container_name: {**container, name: generated}})
283
292
  case.data_generation_method = value.data_generation_method
284
- case.meta = meta
293
+ case.meta = copy(meta)
294
+ case.meta.description = value.description
285
295
  yield case
286
296
 
287
297
 
@@ -79,24 +79,18 @@ class CassetteWriter(EventHandler):
79
79
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
80
80
  if isinstance(event, events.Initialized):
81
81
  # In the beginning we write metadata and start `http_interactions` list
82
- self.queue.put(Initialize())
82
+ self.queue.put(Initialize(seed=event.seed))
83
83
  elif isinstance(event, events.AfterExecution):
84
- # Seed is always present at this point, the original Optional[int] type is there because `TestResult`
85
- # instance is created before `seed` is generated on the hypothesis side
86
- seed = cast(int, event.result.seed)
87
84
  self.queue.put(
88
85
  Process(
89
- seed=seed,
90
86
  correlation_id=event.correlation_id,
91
87
  thread_id=event.thread_id,
92
88
  interactions=event.result.interactions,
93
89
  )
94
90
  )
95
91
  elif isinstance(event, events.AfterStatefulExecution):
96
- seed = cast(int, event.result.seed)
97
92
  self.queue.put(
98
93
  Process(
99
- seed=seed,
100
94
  # Correlation ID is not used in stateful testing
101
95
  correlation_id="",
102
96
  thread_id=event.thread_id,
@@ -118,12 +112,13 @@ class CassetteWriter(EventHandler):
118
112
  class Initialize:
119
113
  """Start up, the first message to make preparations before proceeding the input data."""
120
114
 
115
+ seed: int | None
116
+
121
117
 
122
118
  @dataclass
123
119
  class Process:
124
120
  """A new chunk of data should be processed."""
125
121
 
126
- seed: int
127
122
  correlation_id: str
128
123
  thread_id: int
129
124
  interactions: list[SerializedInteraction]
@@ -219,9 +214,11 @@ def vcr_writer(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: boo
219
214
  )
220
215
  write_double_quoted(output, string)
221
216
 
217
+ seed = "null"
222
218
  while True:
223
219
  item = queue.get()
224
220
  if isinstance(item, Initialize):
221
+ seed = f"'{item.seed}'"
225
222
  stream.write(
226
223
  f"""command: '{get_command_representation()}'
227
224
  recorded_with: 'Schemathesis {SCHEMATHESIS_VERSION}'
@@ -235,10 +232,20 @@ http_interactions:"""
235
232
  stream.write(
236
233
  f"""\n- id: '{current_id}'
237
234
  status: '{status}'
238
- seed: '{item.seed}'
235
+ seed: {seed}
239
236
  thread_id: {item.thread_id}
240
237
  correlation_id: '{item.correlation_id}'
241
238
  data_generation_method: '{interaction.data_generation_method.value}'
239
+ meta:
240
+ description: """
241
+ )
242
+
243
+ if interaction.description is not None:
244
+ write_double_quoted(stream, interaction.description)
245
+ else:
246
+ stream.write("null")
247
+ stream.write(
248
+ f"""
242
249
  phase: {phase}
243
250
  elapsed: '{interaction.response.elapsed if interaction.response else 0}'
244
251
  recorded_at: '{interaction.recorded_at}'
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import os
3
4
  from functools import lru_cache, reduce
4
5
  from operator import or_
5
6
  from typing import TYPE_CHECKING, TypeVar
@@ -8,6 +9,8 @@ if TYPE_CHECKING:
8
9
  from hypothesis import settings
9
10
  from hypothesis import strategies as st
10
11
 
12
+ SCHEMATHESIS_BENCHMARK_SEED = os.environ.get("SCHEMATHESIS_BENCHMARK_SEED")
13
+
11
14
 
12
15
  @lru_cache
13
16
  def default_settings() -> settings:
@@ -33,13 +36,16 @@ def get_single_example(strategy: st.SearchStrategy[T]) -> T: # type: ignore[typ
33
36
 
34
37
 
35
38
  def add_single_example(strategy: st.SearchStrategy[T], examples: list[T]) -> None:
36
- from hypothesis import given
39
+ from hypothesis import given, seed
37
40
 
38
41
  @given(strategy) # type: ignore
39
42
  @default_settings() # type: ignore
40
43
  def example_generating_inner_function(ex: T) -> None:
41
44
  examples.append(ex)
42
45
 
46
+ if SCHEMATHESIS_BENCHMARK_SEED is not None:
47
+ example_generating_inner_function = seed(SCHEMATHESIS_BENCHMARK_SEED)(example_generating_inner_function)
48
+
43
49
  example_generating_inner_function()
44
50
 
45
51
 
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import json
4
+ import functools
4
5
  from contextlib import contextmanager, suppress
5
- from dataclasses import dataclass, field
6
+ from dataclasses import dataclass
6
7
  from functools import lru_cache
7
8
  from itertools import combinations
8
9
  from typing import Any, Generator, Iterator, TypeVar, cast
@@ -18,12 +19,20 @@ from schemathesis.constants import NOT_SET
18
19
  from ._hypothesis import get_single_example
19
20
  from ._methods import DataGenerationMethod
20
21
 
22
+
23
+ def _replace_zero_with_nonzero(x: float) -> float:
24
+ return x or 0.0
25
+
26
+
27
+ def json_recursive_strategy(strategy: st.SearchStrategy) -> st.SearchStrategy:
28
+ return st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3)
29
+
30
+
21
31
  BUFFER_SIZE = 8 * 1024
22
- FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(lambda x: x or 0.0)
32
+ FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(_replace_zero_with_nonzero)
23
33
  NUMERIC_STRATEGY: st.SearchStrategy = st.integers() | FLOAT_STRATEGY
24
34
  JSON_STRATEGY: st.SearchStrategy = st.recursive(
25
- st.none() | st.booleans() | NUMERIC_STRATEGY | st.text(),
26
- lambda strategy: st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3),
35
+ st.none() | st.booleans() | NUMERIC_STRATEGY | st.text(), json_recursive_strategy
27
36
  )
28
37
  ARRAY_STRATEGY: st.SearchStrategy = st.lists(JSON_STRATEGY)
29
38
  OBJECT_STRATEGY: st.SearchStrategy = st.dictionaries(st.text(), JSON_STRATEGY)
@@ -36,16 +45,17 @@ UNKNOWN_PROPERTY_VALUE = 42
36
45
  class GeneratedValue:
37
46
  value: Any
38
47
  data_generation_method: DataGenerationMethod
48
+ description: str
39
49
 
40
- __slots__ = ("value", "data_generation_method")
50
+ __slots__ = ("value", "data_generation_method", "description")
41
51
 
42
52
  @classmethod
43
- def with_positive(cls, value: Any) -> GeneratedValue:
44
- return cls(value, DataGenerationMethod.positive)
53
+ def with_positive(cls, value: Any, *, description: str) -> GeneratedValue:
54
+ return cls(value=value, data_generation_method=DataGenerationMethod.positive, description=description)
45
55
 
46
56
  @classmethod
47
- def with_negative(cls, value: Any) -> GeneratedValue:
48
- return cls(value, DataGenerationMethod.negative)
57
+ def with_negative(cls, value: Any, *, description: str) -> GeneratedValue:
58
+ return cls(value=value, data_generation_method=DataGenerationMethod.negative, description=description)
49
59
 
50
60
 
51
61
  PositiveValue = GeneratedValue.with_positive
@@ -59,7 +69,14 @@ def cached_draw(strategy: st.SearchStrategy) -> Any:
59
69
 
60
70
  @dataclass
61
71
  class CoverageContext:
62
- data_generation_methods: list[DataGenerationMethod] = field(default_factory=DataGenerationMethod.all)
72
+ data_generation_methods: list[DataGenerationMethod]
73
+
74
+ __slots__ = ("data_generation_methods",)
75
+
76
+ def __init__(self, data_generation_methods: list[DataGenerationMethod] | None = None) -> None:
77
+ self.data_generation_methods = (
78
+ data_generation_methods if data_generation_methods is not None else DataGenerationMethod.all()
79
+ )
63
80
 
64
81
  @classmethod
65
82
  def with_positive(cls) -> CoverageContext:
@@ -69,12 +86,8 @@ class CoverageContext:
69
86
  def with_negative(cls) -> CoverageContext:
70
87
  return CoverageContext(data_generation_methods=[DataGenerationMethod.negative])
71
88
 
72
- def generate_from(self, strategy: st.SearchStrategy, cached: bool = False) -> Any:
73
- if cached:
74
- value = cached_draw(strategy)
75
- else:
76
- value = get_single_example(strategy)
77
- return value
89
+ def generate_from(self, strategy: st.SearchStrategy) -> Any:
90
+ return cached_draw(strategy)
78
91
 
79
92
  def generate_from_schema(self, schema: dict) -> Any:
80
93
  return self.generate_from(from_schema(schema))
@@ -117,15 +130,15 @@ def _cover_positive_for_type(
117
130
  yield from cover_schema_iter(ctx, canonical)
118
131
  if enum is not NOT_SET:
119
132
  for value in enum:
120
- yield PositiveValue(value)
133
+ yield PositiveValue(value, description="Enum value")
121
134
  elif const is not NOT_SET:
122
- yield PositiveValue(const)
135
+ yield PositiveValue(const, description="Const value")
123
136
  elif ty is not None:
124
137
  if ty == "null":
125
- yield PositiveValue(None)
138
+ yield PositiveValue(None, description="Value null value")
126
139
  elif ty == "boolean":
127
- yield PositiveValue(True)
128
- yield PositiveValue(False)
140
+ yield PositiveValue(True, description="Valid boolean value")
141
+ yield PositiveValue(False, description="Valid boolean value")
129
142
  elif ty == "string":
130
143
  yield from _positive_string(ctx, schema)
131
144
  elif ty == "integer" or ty == "number":
@@ -200,15 +213,17 @@ def cover_schema_iter(
200
213
  elif key == "maximum":
201
214
  next = value + 1
202
215
  if next not in seen:
203
- yield NegativeValue(next)
216
+ yield NegativeValue(next, description="Value greater than maximum")
204
217
  seen.add(next)
205
218
  elif key == "minimum":
206
219
  next = value - 1
207
220
  if next not in seen:
208
- yield NegativeValue(next)
221
+ yield NegativeValue(next, description="Value smaller than minimum")
209
222
  seen.add(next)
210
223
  elif key == "exclusiveMaximum" or key == "exclusiveMinimum" and value not in seen:
211
- yield NegativeValue(value)
224
+ verb = "greater" if key == "exclusiveMaximum" else "smaller"
225
+ limit = "maximum" if key == "exclusiveMaximum" else "minimum"
226
+ yield NegativeValue(value, description=f"Value {verb} than {limit}")
212
227
  seen.add(value)
213
228
  elif key == "multipleOf":
214
229
  for value_ in _negative_multiple_of(ctx, schema, value):
@@ -221,14 +236,14 @@ def cover_schema_iter(
221
236
  value = ctx.generate_from_schema({**schema, "minLength": value - 1, "maxLength": value - 1})
222
237
  k = _to_hashable_key(value)
223
238
  if k not in seen:
224
- yield NegativeValue(value)
239
+ yield NegativeValue(value, description="String smaller than minLength")
225
240
  seen.add(k)
226
241
  elif key == "maxLength" and value < BUFFER_SIZE:
227
242
  with suppress(InvalidArgument):
228
243
  value = ctx.generate_from_schema({**schema, "minLength": value + 1, "maxLength": value + 1})
229
244
  k = _to_hashable_key(value)
230
245
  if k not in seen:
231
- yield NegativeValue(value)
246
+ yield NegativeValue(value, description="String larger than maxLength")
232
247
  seen.add(k)
233
248
  elif key == "uniqueItems" and value:
234
249
  yield from _negative_unique_items(ctx, schema)
@@ -237,7 +252,10 @@ def cover_schema_iter(
237
252
  yield from _negative_required(ctx, template, value)
238
253
  elif key == "additionalProperties" and not value:
239
254
  template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
240
- yield NegativeValue({**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE})
255
+ yield NegativeValue(
256
+ {**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE},
257
+ description="Object with unexpected properties",
258
+ )
241
259
  elif key == "allOf":
242
260
  nctx = ctx.with_negative()
243
261
  if len(value) == 1:
@@ -291,43 +309,50 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
291
309
  default = schema.get("default")
292
310
  if example or examples or default:
293
311
  if example:
294
- yield PositiveValue(example)
312
+ yield PositiveValue(example, description="Example value")
295
313
  if examples:
296
314
  for example in examples:
297
- yield PositiveValue(example)
315
+ yield PositiveValue(example, description="Example value")
298
316
  if (
299
317
  default
300
318
  and not (example is not None and default == example)
301
319
  and not (examples is not None and any(default == ex for ex in examples))
302
320
  ):
303
- yield PositiveValue(default)
321
+ yield PositiveValue(default, description="Default value")
304
322
  elif not min_length and not max_length:
305
323
  # Default positive value
306
- yield PositiveValue(ctx.generate_from_schema(schema))
324
+ yield PositiveValue(ctx.generate_from_schema(schema), description="Valid string")
307
325
  elif "pattern" in schema:
308
326
  # Without merging `maxLength` & `minLength` into a regex it is problematic
309
327
  # to generate a valid value as the unredlying machinery will resort to filtering
310
328
  # and it is unlikely that it will generate a string of that length
311
- yield PositiveValue(ctx.generate_from_schema(schema))
329
+ yield PositiveValue(ctx.generate_from_schema(schema), description="Valid string")
312
330
  return
313
331
 
314
332
  seen = set()
315
333
 
316
334
  if min_length is not None and min_length < BUFFER_SIZE:
317
335
  # Exactly the minimum length
318
- yield PositiveValue(ctx.generate_from_schema({**schema, "maxLength": min_length}))
336
+ yield PositiveValue(
337
+ ctx.generate_from_schema({**schema, "maxLength": min_length}), description="Minimum length string"
338
+ )
319
339
  seen.add(min_length)
320
340
 
321
341
  # One character more than minimum if possible
322
342
  larger = min_length + 1
323
343
  if larger < BUFFER_SIZE and larger not in seen and (not max_length or larger <= max_length):
324
- yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}))
344
+ yield PositiveValue(
345
+ ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}),
346
+ description="Near-boundary length string",
347
+ )
325
348
  seen.add(larger)
326
349
 
327
350
  if max_length is not None:
328
351
  # Exactly the maximum length
329
352
  if max_length < BUFFER_SIZE and max_length not in seen:
330
- yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": max_length}))
353
+ yield PositiveValue(
354
+ ctx.generate_from_schema({**schema, "minLength": max_length}), description="Maximum length string"
355
+ )
331
356
  seen.add(max_length)
332
357
 
333
358
  # One character less than maximum if possible
@@ -337,7 +362,10 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
337
362
  and smaller not in seen
338
363
  and (smaller > 0 and (min_length is None or smaller >= min_length))
339
364
  ):
340
- yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller}))
365
+ yield PositiveValue(
366
+ ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller}),
367
+ description="Near-boundary length string",
368
+ )
341
369
  seen.add(smaller)
342
370
 
343
371
 
@@ -367,19 +395,19 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
367
395
 
368
396
  if example or examples or default:
369
397
  if example:
370
- yield PositiveValue(example)
398
+ yield PositiveValue(example, description="Example value")
371
399
  if examples:
372
400
  for example in examples:
373
- yield PositiveValue(example)
401
+ yield PositiveValue(example, description="Example value")
374
402
  if (
375
403
  default
376
404
  and not (example is not None and default == example)
377
405
  and not (examples is not None and any(default == ex for ex in examples))
378
406
  ):
379
- yield PositiveValue(default)
407
+ yield PositiveValue(default, description="Default value")
380
408
  elif not minimum and not maximum:
381
409
  # Default positive value
382
- yield PositiveValue(ctx.generate_from_schema(schema))
410
+ yield PositiveValue(ctx.generate_from_schema(schema), description="Valid number")
383
411
 
384
412
  seen = set()
385
413
 
@@ -390,7 +418,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
390
418
  else:
391
419
  smallest = minimum
392
420
  seen.add(smallest)
393
- yield PositiveValue(smallest)
421
+ yield PositiveValue(smallest, description="Minimum value")
394
422
 
395
423
  # One more than minimum if possible
396
424
  if multiple_of is not None:
@@ -399,7 +427,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
399
427
  larger = minimum + 1
400
428
  if larger not in seen and (not maximum or larger <= maximum):
401
429
  seen.add(larger)
402
- yield PositiveValue(larger)
430
+ yield PositiveValue(larger, description="Near-boundary number")
403
431
 
404
432
  if maximum is not None:
405
433
  # Exactly the maximum
@@ -409,7 +437,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
409
437
  largest = maximum
410
438
  if largest not in seen:
411
439
  seen.add(largest)
412
- yield PositiveValue(largest)
440
+ yield PositiveValue(largest, description="Maximum value")
413
441
 
414
442
  # One less than maximum if possible
415
443
  if multiple_of is not None:
@@ -418,7 +446,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
418
446
  smaller = maximum - 1
419
447
  if smaller not in seen and (smaller > 0 and (minimum is None or smaller >= minimum)):
420
448
  seen.add(smaller)
421
- yield PositiveValue(smaller)
449
+ yield PositiveValue(smaller, description="Near-boundary number")
422
450
 
423
451
 
424
452
  def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Generator[GeneratedValue, None, None]:
@@ -429,18 +457,18 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
429
457
 
430
458
  if example or examples or default:
431
459
  if example:
432
- yield PositiveValue(example)
460
+ yield PositiveValue(example, description="Example value")
433
461
  if examples:
434
462
  for example in examples:
435
- yield PositiveValue(example)
463
+ yield PositiveValue(example, description="Example value")
436
464
  if (
437
465
  default
438
466
  and not (example is not None and default == example)
439
467
  and not (examples is not None and any(default == ex for ex in examples))
440
468
  ):
441
- yield PositiveValue(default)
469
+ yield PositiveValue(default, description="Default value")
442
470
  else:
443
- yield PositiveValue(template)
471
+ yield PositiveValue(template, description="Valid array")
444
472
  seen.add(len(template))
445
473
 
446
474
  # Boundary and near-boundary sizes
@@ -452,12 +480,18 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
452
480
  # One item more than minimum if possible
453
481
  larger = min_items + 1
454
482
  if larger not in seen and (max_items is None or larger <= max_items):
455
- yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger}))
483
+ yield PositiveValue(
484
+ ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger}),
485
+ description="Near-boundary items array",
486
+ )
456
487
  seen.add(larger)
457
488
 
458
489
  if max_items is not None:
459
490
  if max_items < BUFFER_SIZE and max_items not in seen:
460
- yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": max_items}))
491
+ yield PositiveValue(
492
+ ctx.generate_from_schema({**schema, "minItems": max_items}),
493
+ description="Maximum items array",
494
+ )
461
495
  seen.add(max_items)
462
496
 
463
497
  # One item smaller than maximum if possible
@@ -468,7 +502,10 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
468
502
  and smaller not in seen
469
503
  and (min_items is None or smaller >= min_items)
470
504
  ):
471
- yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller}))
505
+ yield PositiveValue(
506
+ ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller}),
507
+ description="Near-boundary items array",
508
+ )
472
509
  seen.add(smaller)
473
510
 
474
511
 
@@ -479,19 +516,18 @@ def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Gene
479
516
 
480
517
  if example or examples or default:
481
518
  if example:
482
- yield PositiveValue(example)
519
+ yield PositiveValue(example, description="Example value")
483
520
  if examples:
484
521
  for example in examples:
485
- yield PositiveValue(example)
522
+ yield PositiveValue(example, description="Example value")
486
523
  if (
487
524
  default
488
525
  and not (example is not None and default == example)
489
526
  and not (examples is not None and any(default == ex for ex in examples))
490
527
  ):
491
- yield PositiveValue(default)
492
-
528
+ yield PositiveValue(default, description="Default value")
493
529
  else:
494
- yield PositiveValue(template)
530
+ yield PositiveValue(template, description="Valid object")
495
531
 
496
532
  properties = schema.get("properties", {})
497
533
  required = set(schema.get("required", []))
@@ -502,22 +538,24 @@ def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Gene
502
538
  for name in optional:
503
539
  combo = {k: v for k, v in template.items() if k in required or k == name}
504
540
  if combo != template:
505
- yield PositiveValue(combo)
541
+ yield PositiveValue(combo, description=f"Object with all required properties and '{name}'")
506
542
  # Generate one combination for each size from 2 to N-1
507
543
  for selection in select_combinations(optional):
508
544
  combo = {k: v for k, v in template.items() if k in required or k in selection}
509
- yield PositiveValue(combo)
545
+ yield PositiveValue(combo, description="Object with all required and a subset of optional properties")
510
546
  # Generate only required properties
511
547
  if set(properties) != required:
512
548
  only_required = {k: v for k, v in template.items() if k in required}
513
- yield PositiveValue(only_required)
549
+ yield PositiveValue(only_required, description="Object with only required properties")
514
550
  seen = set()
515
551
  for name, sub_schema in properties.items():
516
552
  seen.add(_to_hashable_key(template.get(name)))
517
553
  for new in cover_schema_iter(ctx, sub_schema):
518
554
  key = _to_hashable_key(new.value)
519
555
  if key not in seen:
520
- yield PositiveValue({**template, name: new.value})
556
+ yield PositiveValue(
557
+ {**template, name: new.value}, description=f"Object with valid '{name}' value: {new.description}"
558
+ )
521
559
  seen.add(key)
522
560
  seen.clear()
523
561
 
@@ -528,9 +566,12 @@ def select_combinations(optional: list[str]) -> Iterator[tuple[str, ...]]:
528
566
 
529
567
 
530
568
  def _negative_enum(ctx: CoverageContext, value: list) -> Generator[GeneratedValue, None, None]:
531
- strategy = JSON_STRATEGY.filter(lambda x: x not in value)
569
+ def is_not_in_value(x: Any) -> bool:
570
+ return x not in value
571
+
572
+ strategy = JSON_STRATEGY.filter(is_not_in_value)
532
573
  # The exact negative value is not important here
533
- yield NegativeValue(ctx.generate_from(strategy, cached=True))
574
+ yield NegativeValue(ctx.generate_from(strategy), description="Invalid enum value")
534
575
 
535
576
 
536
577
  def _negative_properties(
@@ -539,11 +580,17 @@ def _negative_properties(
539
580
  nctx = ctx.with_negative()
540
581
  for key, sub_schema in properties.items():
541
582
  for value in cover_schema_iter(nctx, sub_schema):
542
- yield NegativeValue({**template, key: value.value})
583
+ yield NegativeValue(
584
+ {**template, key: value.value},
585
+ description=f"Object with invalid '{key}' value: {value.description}",
586
+ )
543
587
 
544
588
 
545
589
  def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator[GeneratedValue, None, None]:
546
- yield NegativeValue(ctx.generate_from(st.text().filter(lambda x: x != pattern), cached=True))
590
+ yield NegativeValue(
591
+ ctx.generate_from(st.text().filter(pattern.__ne__)),
592
+ description=f"Value not matching the '{pattern}' pattern",
593
+ )
547
594
 
548
595
 
549
596
  def _with_negated_key(schema: dict, key: str, value: Any) -> dict:
@@ -553,19 +600,33 @@ def _with_negated_key(schema: dict, key: str, value: Any) -> dict:
553
600
  def _negative_multiple_of(
554
601
  ctx: CoverageContext, schema: dict, multiple_of: int | float
555
602
  ) -> Generator[GeneratedValue, None, None]:
556
- yield NegativeValue(ctx.generate_from_schema(_with_negated_key(schema, "multipleOf", multiple_of)))
603
+ yield NegativeValue(
604
+ ctx.generate_from_schema(_with_negated_key(schema, "multipleOf", multiple_of)),
605
+ description=f"Non-multiple of {multiple_of}",
606
+ )
557
607
 
558
608
 
559
609
  def _negative_unique_items(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
560
610
  unique = ctx.generate_from_schema({**schema, "type": "array", "minItems": 1, "maxItems": 1})
561
- yield NegativeValue(unique + unique)
611
+ yield NegativeValue(unique + unique, description="Non-unique items")
562
612
 
563
613
 
564
614
  def _negative_required(
565
615
  ctx: CoverageContext, template: dict, required: list[str]
566
616
  ) -> Generator[GeneratedValue, None, None]:
567
617
  for key in required:
568
- yield NegativeValue({k: v for k, v in template.items() if k != key})
618
+ yield NegativeValue(
619
+ {k: v for k, v in template.items() if k != key},
620
+ description=f"Missing required property: {key}",
621
+ )
622
+
623
+
624
+ def _is_invalid_hostname(v: Any) -> bool:
625
+ return v == "" or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, "hostname")
626
+
627
+
628
+ def _is_invalid_format(v: Any, format: str) -> bool:
629
+ return not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
569
630
 
570
631
 
571
632
  def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generator[GeneratedValue, None, None]:
@@ -574,11 +635,15 @@ def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generat
574
635
  without_format.setdefault("type", "string")
575
636
  strategy = from_schema(without_format)
576
637
  if format in jsonschema.Draft202012Validator.FORMAT_CHECKER.checkers:
577
- strategy = strategy.filter(
578
- lambda v: (format == "hostname" and v == "")
579
- or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
580
- )
581
- yield NegativeValue(ctx.generate_from(strategy))
638
+ if format == "hostname":
639
+ strategy = strategy.filter(_is_invalid_hostname)
640
+ else:
641
+ strategy = strategy.filter(functools.partial(_is_invalid_format, format=format))
642
+ yield NegativeValue(ctx.generate_from(strategy), description=f"Value not matching the '{format}' format")
643
+
644
+
645
+ def _is_non_integer_float(x: float) -> bool:
646
+ return x != int(x)
582
647
 
583
648
 
584
649
  def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Generator[GeneratedValue, None, None]:
@@ -600,13 +665,13 @@ def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Gene
600
665
  if "number" in types:
601
666
  del strategies["integer"]
602
667
  if "integer" in types:
603
- strategies["number"] = FLOAT_STRATEGY.filter(lambda x: x != int(x))
668
+ strategies["number"] = FLOAT_STRATEGY.filter(_is_non_integer_float)
604
669
  for strat in strategies.values():
605
- value = ctx.generate_from(strat, cached=True)
670
+ value = ctx.generate_from(strat)
606
671
  hashed = _to_hashable_key(value)
607
672
  if hashed in seen:
608
673
  continue
609
- yield NegativeValue(value)
674
+ yield NegativeValue(value, description="Incorrect type")
610
675
  seen.add(hashed)
611
676
 
612
677
 
@@ -6,6 +6,7 @@ from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING, Callable, Optional
7
7
 
8
8
  if TYPE_CHECKING:
9
+ from requests.auth import HTTPDigestAuth
9
10
  from requests.structures import CaseInsensitiveDict
10
11
 
11
12
  from ..models import Case
@@ -23,7 +24,7 @@ class CheckContext:
23
24
  Provides access to broader test execution data beyond individual test cases.
24
25
  """
25
26
 
26
- auth: RawAuth | None = None
27
+ auth: HTTPDigestAuth | RawAuth | None = None
27
28
  headers: CaseInsensitiveDict | None = None
28
29
 
29
30
 
schemathesis/models.py CHANGED
@@ -154,8 +154,9 @@ class GenerationMetadata:
154
154
  cookies: DataGenerationMethod | None
155
155
  body: DataGenerationMethod | None
156
156
  phase: TestPhase
157
+ description: str | None
157
158
 
158
- __slots__ = ("query", "path_parameters", "headers", "cookies", "body", "phase")
159
+ __slots__ = ("query", "path_parameters", "headers", "cookies", "body", "phase", "description")
159
160
 
160
161
 
161
162
  @dataclass(repr=False)
@@ -1017,6 +1018,7 @@ class Interaction:
1017
1018
  status: Status
1018
1019
  data_generation_method: DataGenerationMethod
1019
1020
  phase: TestPhase | None
1021
+ description: str | None
1020
1022
  recorded_at: str = field(default_factory=lambda: datetime.datetime.now(TIMEZONE).isoformat())
1021
1023
 
1022
1024
  @classmethod
@@ -1046,6 +1048,7 @@ class Interaction:
1046
1048
  checks=checks,
1047
1049
  data_generation_method=cast(DataGenerationMethod, case.data_generation_method),
1048
1050
  phase=case.meta.phase if case.meta is not None else None,
1051
+ description=case.meta.description if case.meta is not None else None,
1049
1052
  )
1050
1053
 
1051
1054
  @classmethod
@@ -1069,6 +1072,7 @@ class Interaction:
1069
1072
  checks=checks,
1070
1073
  data_generation_method=cast(DataGenerationMethod, case.data_generation_method),
1071
1074
  phase=case.meta.phase if case.meta is not None else None,
1075
+ description=case.meta.description if case.meta is not None else None,
1072
1076
  )
1073
1077
 
1074
1078
 
@@ -395,6 +395,7 @@ class SerializedInteraction:
395
395
  status: Status
396
396
  data_generation_method: DataGenerationMethod
397
397
  phase: TestPhase | None
398
+ description: str | None
398
399
  recorded_at: str
399
400
 
400
401
  @classmethod
@@ -406,6 +407,7 @@ class SerializedInteraction:
406
407
  status=interaction.status,
407
408
  data_generation_method=interaction.data_generation_method,
408
409
  phase=interaction.phase,
410
+ description=interaction.description,
409
411
  recorded_at=interaction.recorded_at,
410
412
  )
411
413
 
@@ -215,6 +215,7 @@ def get_case_strategy(
215
215
  cookies=cookies_.generator,
216
216
  body=body_.generator,
217
217
  phase=phase,
218
+ description=None,
218
219
  ),
219
220
  )
220
221
  auth_context = auths.AuthContext(
@@ -1330,6 +1330,8 @@ OPENAPI_30 = {
1330
1330
  },
1331
1331
  },
1332
1332
  }
1333
+ # Generated from the updated schema.yaml from 0035208, which includes unpublished bugfixes
1334
+ # https://github.com/OAI/OpenAPI-Specification/blob/0035208611701b4f7f2c959eb99a8725cca41e6e/schemas/v3.1/schema.yaml
1333
1335
  OPENAPI_31 = {
1334
1336
  "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07",
1335
1337
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1345,7 +1347,7 @@ OPENAPI_31 = {
1345
1347
  },
1346
1348
  "servers": {"type": "array", "items": {"$ref": "#/$defs/server"}, "default": [{"url": "/"}]},
1347
1349
  "paths": {"$ref": "#/$defs/paths"},
1348
- "webhooks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item-or-reference"}},
1350
+ "webhooks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item"}},
1349
1351
  "components": {"$ref": "#/$defs/components"},
1350
1352
  "security": {"type": "array", "items": {"$ref": "#/$defs/security-requirement"}},
1351
1353
  "tags": {"type": "array", "items": {"$ref": "#/$defs/tag"}},
@@ -1400,7 +1402,7 @@ OPENAPI_31 = {
1400
1402
  "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object",
1401
1403
  "type": "object",
1402
1404
  "properties": {
1403
- "url": {"type": "string", "format": "uri-reference"},
1405
+ "url": {"type": "string"},
1404
1406
  "description": {"type": "string"},
1405
1407
  "variables": {"type": "object", "additionalProperties": {"$ref": "#/$defs/server-variable"}},
1406
1408
  },
@@ -1439,7 +1441,7 @@ OPENAPI_31 = {
1439
1441
  },
1440
1442
  "links": {"type": "object", "additionalProperties": {"$ref": "#/$defs/link-or-reference"}},
1441
1443
  "callbacks": {"type": "object", "additionalProperties": {"$ref": "#/$defs/callbacks-or-reference"}},
1442
- "pathItems": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item-or-reference"}},
1444
+ "pathItems": {"type": "object", "additionalProperties": {"$ref": "#/$defs/path-item"}},
1443
1445
  },
1444
1446
  "patternProperties": {
1445
1447
  "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": {
@@ -1461,6 +1463,7 @@ OPENAPI_31 = {
1461
1463
  "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object",
1462
1464
  "type": "object",
1463
1465
  "properties": {
1466
+ "$ref": {"type": "string", "format": "uri-reference"},
1464
1467
  "summary": {"type": "string"},
1465
1468
  "description": {"type": "string"},
1466
1469
  "servers": {"type": "array", "items": {"$ref": "#/$defs/server"}},
@@ -1477,11 +1480,6 @@ OPENAPI_31 = {
1477
1480
  "$ref": "#/$defs/specification-extensions",
1478
1481
  "unevaluatedProperties": False,
1479
1482
  },
1480
- "path-item-or-reference": {
1481
- "if": {"type": "object", "required": ["$ref"]},
1482
- "then": {"$ref": "#/$defs/reference"},
1483
- "else": {"$ref": "#/$defs/path-item"},
1484
- },
1485
1483
  "operation": {
1486
1484
  "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object",
1487
1485
  "type": "object",
@@ -1542,7 +1540,6 @@ OPENAPI_31 = {
1542
1540
  "if": {"properties": {"in": {"const": "path"}}, "required": ["in"]},
1543
1541
  "then": {
1544
1542
  "properties": {
1545
- "name": {"pattern": "[^/#?]+$"},
1546
1543
  "style": {"default": "simple", "enum": ["matrix", "label", "simple"]},
1547
1544
  "required": {"const": True},
1548
1545
  },
@@ -1662,7 +1659,7 @@ OPENAPI_31 = {
1662
1659
  "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object",
1663
1660
  "type": "object",
1664
1661
  "$ref": "#/$defs/specification-extensions",
1665
- "additionalProperties": {"$ref": "#/$defs/path-item-or-reference"},
1662
+ "additionalProperties": {"$ref": "#/$defs/path-item"},
1666
1663
  },
1667
1664
  "callbacks-or-reference": {
1668
1665
  "if": {"type": "object", "required": ["$ref"]},
@@ -1755,7 +1752,6 @@ OPENAPI_31 = {
1755
1752
  "summary": {"type": "string"},
1756
1753
  "description": {"type": "string"},
1757
1754
  },
1758
- "unevaluatedProperties": False,
1759
1755
  },
1760
1756
  "schema": {
1761
1757
  "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object",
@@ -12,6 +12,7 @@ from hypothesis.control import current_build_context
12
12
  from hypothesis.errors import Flaky, Unsatisfiable
13
13
 
14
14
  from ..exceptions import CheckFailed
15
+ from ..internal.checks import CheckContext
15
16
  from ..targets import TargetMetricCollector
16
17
  from . import events
17
18
  from .config import StatefulTestRunnerConfig
@@ -113,6 +114,7 @@ def _execute_state_machine_loop(
113
114
  ) -> None:
114
115
  """Execute the state machine testing loop."""
115
116
  from hypothesis import reporting
117
+ from requests.structures import CaseInsensitiveDict
116
118
 
117
119
  from ..transports import RequestsTransport
118
120
 
@@ -129,6 +131,7 @@ def _execute_state_machine_loop(
129
131
  if config.auth is not None:
130
132
  session.auth = config.auth
131
133
  call_kwargs["session"] = session
134
+ check_ctx = CheckContext(auth=config.auth, headers=CaseInsensitiveDict(config.headers) if config.headers else None)
132
135
 
133
136
  class _InstrumentedStateMachine(state_machine): # type: ignore[valid-type,misc]
134
137
  """State machine with additional hooks for emitting events."""
@@ -223,7 +226,8 @@ def _execute_state_machine_loop(
223
226
  validate_response(
224
227
  response=response,
225
228
  case=case,
226
- ctx=ctx,
229
+ runner_ctx=ctx,
230
+ check_ctx=check_ctx,
227
231
  checks=config.checks,
228
232
  additional_checks=additional_checks,
229
233
  max_response_time=config.max_response_time,
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from ..exceptions import CheckFailed, get_grouped_exception
6
6
  from ..internal.checks import CheckContext
@@ -17,27 +17,24 @@ def validate_response(
17
17
  *,
18
18
  response: GenericResponse,
19
19
  case: Case,
20
- ctx: RunnerContext,
20
+ runner_ctx: RunnerContext,
21
+ check_ctx: CheckContext,
21
22
  checks: tuple[CheckFunction, ...],
22
23
  additional_checks: tuple[CheckFunction, ...] = (),
23
24
  max_response_time: int | None = None,
24
- headers: dict[str, Any] | None = None,
25
25
  ) -> None:
26
26
  """Validate the response against the provided checks."""
27
- from requests.structures import CaseInsensitiveDict
28
-
29
27
  from .._compat import MultipleFailures
30
28
  from ..checks import _make_max_response_time_failure_message
31
29
  from ..failures import ResponseTimeExceeded
32
30
  from ..models import Check, Status
33
31
 
34
32
  exceptions: list[CheckFailed | AssertionError] = []
35
- check_results = ctx.checks_for_step
36
- check_ctx = CheckContext(headers=CaseInsensitiveDict(headers) if headers else None)
33
+ check_results = runner_ctx.checks_for_step
37
34
 
38
35
  def _on_failure(exc: CheckFailed | AssertionError, message: str, context: FailureContext | None) -> None:
39
36
  exceptions.append(exc)
40
- if ctx.is_seen_in_suite(exc):
37
+ if runner_ctx.is_seen_in_suite(exc):
41
38
  return
42
39
  failed_check = Check(
43
40
  name=name,
@@ -49,9 +46,9 @@ def validate_response(
49
46
  context=context,
50
47
  request=None,
51
48
  )
52
- ctx.add_failed_check(failed_check)
49
+ runner_ctx.add_failed_check(failed_check)
53
50
  check_results.append(failed_check)
54
- ctx.mark_as_seen_in_suite(exc)
51
+ runner_ctx.mark_as_seen_in_suite(exc)
55
52
 
56
53
  def _on_passed(_name: str, _case: Case) -> None:
57
54
  passed_check = Check(
@@ -72,16 +69,16 @@ def validate_response(
72
69
  if not skip_check:
73
70
  _on_passed(name, copied_case)
74
71
  except CheckFailed as exc:
75
- if ctx.is_seen_in_run(exc):
72
+ if runner_ctx.is_seen_in_run(exc):
76
73
  continue
77
74
  _on_failure(exc, str(exc), exc.context)
78
75
  except AssertionError as exc:
79
- if ctx.is_seen_in_run(exc):
76
+ if runner_ctx.is_seen_in_run(exc):
80
77
  continue
81
78
  _on_failure(exc, str(exc) or f"Custom check failed: `{name}`", None)
82
79
  except MultipleFailures as exc:
83
80
  for subexc in exc.exceptions:
84
- if ctx.is_seen_in_run(subexc):
81
+ if runner_ctx.is_seen_in_run(subexc):
85
82
  continue
86
83
  _on_failure(subexc, str(subexc), subexc.context)
87
84
 
@@ -93,7 +90,7 @@ def validate_response(
93
90
  try:
94
91
  raise AssertionError(message)
95
92
  except AssertionError as _exc:
96
- if not ctx.is_seen_in_run(_exc):
93
+ if not runner_ctx.is_seen_in_run(_exc):
97
94
  _on_failure(_exc, message, context)
98
95
  else:
99
96
  _on_passed("max_response_time", case)
@@ -7,7 +7,7 @@ from contextlib import contextmanager
7
7
  from dataclasses import dataclass
8
8
  from datetime import timedelta
9
9
  from inspect import iscoroutinefunction
10
- from typing import TYPE_CHECKING, Any, Generator, Optional, Protocol, TypeVar, cast
10
+ from typing import TYPE_CHECKING, Any, Generator, Protocol, TypeVar, cast
11
11
  from urllib.parse import urlparse
12
12
 
13
13
  from .. import failures
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: schemathesis
3
- Version: 3.36.2
3
+ Version: 3.36.4
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=UW2Bq8hDDkcBeAAA7PzpBFXkOOxkmHox-mfQwzHDjL0,1914
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=Sj7pGspDGeevcPgLkCF1pV7T5HleF84nZd7FbDUs_vE,14646
4
+ schemathesis/_hypothesis.py,sha256=TUGODyKJzTSnUQPWt_uj6-MBXCPthquJXGiCobVdFh0,14930
5
5
  schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
6
6
  schemathesis/_override.py,sha256=TAjYB3eJQmlw9K_xiR9ptt9Wj7if4U7UFlUhGjpBAoM,1625
7
7
  schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
@@ -17,7 +17,7 @@ schemathesis/graphql.py,sha256=XiuKcfoOB92iLFC8zpz2msLkM0_V0TLdxPNBqrrGZ8w,216
17
17
  schemathesis/hooks.py,sha256=qXyVRfJdhsLk1GuJX47VAqkX0VPm6X6fK-cXhEnFLT4,14765
18
18
  schemathesis/lazy.py,sha256=uE8ef_7U_9ovs0-7UA7ssIiiDipJurJFHuxaUFOUETo,18956
19
19
  schemathesis/loaders.py,sha256=MoEhcdOEBJxNRn5X-ZNhWB9jZDHQQNpkNfEdQjf_NDw,4590
20
- schemathesis/models.py,sha256=YNZ9EXVw0WreTy_3hw2aZ9OPS-0W7xePlh5h0dLmgvs,46411
20
+ schemathesis/models.py,sha256=mgufFwK6pVWn6hjfJIJwbk0qZM-XUeUaVVaOOEPaR50,46646
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=Lycn1VVfula9B6XpzkxTHja7CZ7RHqbUh9kBic0Yi4M,9056
@@ -30,7 +30,7 @@ schemathesis/utils.py,sha256=8RkTZ9Ft5IUaGkxABhh34oU7WO2ouMsfgtvFPTx9alI,4875
30
30
  schemathesis/cli/__init__.py,sha256=OC6QO38QDf55DTIVwrWiQKz8BfTD5QcK574m67NCE2w,72862
31
31
  schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
32
32
  schemathesis/cli/callbacks.py,sha256=grMKlx_iGiJA4oJsYt_q8l354Y8Nb11IBvOKwbD0jOA,15192
33
- schemathesis/cli/cassettes.py,sha256=rephr8daCVGV0alG9XaNBpUOwF6AB6q_z81a-p4VZeo,19403
33
+ schemathesis/cli/cassettes.py,sha256=jD1JTkkEALUUEyzyuJ-KuxgntfGodILUuOu3C9HKjIw,19412
34
34
  schemathesis/cli/constants.py,sha256=wk-0GsoJIel8wFFerQ6Kf_6eAYUtIWkwMFwyAqv3yj4,1635
35
35
  schemathesis/cli/context.py,sha256=j_lvYQiPa6Q7P4P_IGCM9V2y2gJSpDbpxIIzR5oFB2I,2567
36
36
  schemathesis/cli/debug.py,sha256=_YA-bX1ujHl4bqQDEum7M-I2XHBTEGbvgkhvcvKhmgU,658
@@ -58,11 +58,11 @@ schemathesis/fixups/__init__.py,sha256=RP5QYJVJhp8LXjhH89fCRaIVU26dHCy74jD9seoYM
58
58
  schemathesis/fixups/fast_api.py,sha256=mn-KzBqnR8jl4W5fY-_ZySabMDMUnpzCIESMHnlvE1c,1304
59
59
  schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZETA,745
60
60
  schemathesis/generation/__init__.py,sha256=29Zys_tD6kfngaC4zHeC6TOBZQcmo7CWm7KDSYsHStQ,1581
61
- schemathesis/generation/_hypothesis.py,sha256=QDBzpcM9eXPgLGGdCPdGlxCtfMXD4YBN9_6Oz73lofI,1406
61
+ schemathesis/generation/_hypothesis.py,sha256=Aaol5w3TEOVZn8znrnnJCYfrll8eALW0dCRtz3k0Eis,1661
62
62
  schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
63
- schemathesis/generation/coverage.py,sha256=F_ABsBsQ7k4dR_sGI2ZNTUNRCjxpAPh5V80MYXx8F20,24758
63
+ schemathesis/generation/coverage.py,sha256=rq7em0ifTQMZxprBE4NIJWFiV6PquKFcnMMSuR2-ohI,28268
64
64
  schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
65
- schemathesis/internal/checks.py,sha256=m6lY6x2Pkz6AjU8Hs-UMSDJZEOq8EcgZOCp6BQA7p_g,1668
65
+ schemathesis/internal/checks.py,sha256=qxJ5Ndeiveh_BT0QZq0Vbv72-ZTpbevOY48XKGQC9Ec,1730
66
66
  schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
67
67
  schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
68
68
  schemathesis/internal/deprecation.py,sha256=Ty5VBFBlufkITpP0WWTPIPbnB7biDi0kQgXVYWZp820,1273
@@ -75,7 +75,7 @@ schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QA
75
75
  schemathesis/runner/__init__.py,sha256=dLvb4FvH1zvYyVj5i7naR0ehfKL7K8QBKKbBNp_ClY8,21536
76
76
  schemathesis/runner/events.py,sha256=cRKKSDvHvKLBIyFBz-J0JtAKshbGGKco9eaMyLCgzsY,11734
77
77
  schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
78
- schemathesis/runner/serialization.py,sha256=Rn8wUpxe8saWUBfSI60jK7-qPR-D2pY1ad1hD8qTHhE,20418
78
+ schemathesis/runner/serialization.py,sha256=jHpfm1PgPAmorNkF8_rkzIYoeA43jpbSKeh5Hm5nqF0,20495
79
79
  schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
80
80
  schemathesis/runner/impl/context.py,sha256=KT3Dl1HIUM29Jpp_DwfoSx_NbWFH_7s6gw-p2Sr-N24,2505
81
81
  schemathesis/runner/impl/core.py,sha256=bAPwfhLJrXLlPN6BDNGyQoO9v35YlmeuxAmyFTNQg5Y,47197
@@ -104,11 +104,11 @@ schemathesis/specs/graphql/schemas.py,sha256=b7QwglKbcYQCMjuYmqDsVoFu2o4xaA_kduU
104
104
  schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzMSpnVoRWvxy0,1635
105
105
  schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
106
106
  schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
107
- schemathesis/specs/openapi/_hypothesis.py,sha256=ZbLo8hDf4WVmRKmKF6rhK47xetkVJmE59Ghu_p7kLCw,24293
107
+ schemathesis/specs/openapi/_hypothesis.py,sha256=Ym1d3GXlabOSbDk_AEkmkZGl9EMIDpumciLZtfYNdUI,24323
108
108
  schemathesis/specs/openapi/checks.py,sha256=-4qOzkova0e4QSqdgsoUiOv2bg57HZmzbpAiAeotc3Q,22288
109
109
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
110
110
  schemathesis/specs/openapi/converter.py,sha256=NkrzBNjtmVwQTeE73NOtwB_puvQTjxxqqrc7gD_yscc,3241
111
- schemathesis/specs/openapi/definitions.py,sha256=nEsCKn_LgqYjZ9nNWp-8KUIrB4S94pT3GsV5A8UIzDw,94043
111
+ schemathesis/specs/openapi/definitions.py,sha256=WTkWwCgTc3OMxfKsqh6YDoGfZMTThSYrHGp8h0vLAK0,93935
112
112
  schemathesis/specs/openapi/examples.py,sha256=FwhPWca7bpdHpUp_LRoK09DVgusojO3aXXhXYrK373I,20354
113
113
  schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
114
114
  schemathesis/specs/openapi/links.py,sha256=a8JmWM9aZhrR5CfyIh6t2SkfonMLfYKOScXY2XlZYN0,17749
@@ -140,19 +140,19 @@ schemathesis/stateful/__init__.py,sha256=HBg-h131EI8IipHQgufSaXe-CrFTKmffPVsoEFj
140
140
  schemathesis/stateful/config.py,sha256=huYzqDoD6x20p_VNAR79NgxPwUFO8UXoc3_z4BEuHqU,3586
141
141
  schemathesis/stateful/context.py,sha256=vJ9nxTTjI5wo7A6PBGCvVVO_7y-ELs3XERi9PxLzykA,5085
142
142
  schemathesis/stateful/events.py,sha256=CyYvyQebOaeTn6UevaB7HXOrUhxCWbqXMfQ7pZK7fV8,6727
143
- schemathesis/stateful/runner.py,sha256=doEzd7IOfrW1zPzRC0WOqR-N1x5FmaJVGOZur-za6Ao,12352
143
+ schemathesis/stateful/runner.py,sha256=e3vvRrx0NwN20RdC0cC4mZjcsYezwkhoDwE1cCVOAlw,12615
144
144
  schemathesis/stateful/sink.py,sha256=bHYlgh-fMwg1Srxk_XGs0-WV34YccotwH9PGrxCK57A,2474
145
145
  schemathesis/stateful/state_machine.py,sha256=PFztY82W5enuXjO6k4Mz8fbHmDJ7Z8OLYZRWtuBeyjg,12956
146
146
  schemathesis/stateful/statistic.py,sha256=2-uU5xpT9CbMulKgJWLZN6MUpC0Fskf5yXTt4ef4NFA,542
147
- schemathesis/stateful/validation.py,sha256=mNiT8fT2aaxNwhyj2n2G-wxqtMPYxurtH4PYZNRXCzI,3848
148
- schemathesis/transports/__init__.py,sha256=UKt3GJboaPbwcX0vlBZB3i1rFF6tTBQkwDmzB-3o7ns,12892
147
+ schemathesis/stateful/validation.py,sha256=23qSZjC1_xRmtCX4OqsyG6pGxdlo6IZYid695ZpDQyU,3747
148
+ schemathesis/transports/__init__.py,sha256=ybH90TrGwODO5s94UMEX2P2HX-6Jb66X5UUOgKTbZz8,12882
149
149
  schemathesis/transports/asgi.py,sha256=bwW9vMd1h89Jh7I4jHJVwSNUQzHvc7-JOD5u4hSHZd8,212
150
150
  schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZwo,1143
151
151
  schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
152
152
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
153
153
  schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
154
- schemathesis-3.36.2.dist-info/METADATA,sha256=eoWwbh8dfwwyl3EyHoJsV_MUm4zvKYGTMMehbfOJyyQ,12904
155
- schemathesis-3.36.2.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
- schemathesis-3.36.2.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
- schemathesis-3.36.2.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
- schemathesis-3.36.2.dist-info/RECORD,,
154
+ schemathesis-3.36.4.dist-info/METADATA,sha256=_FwLfobUt1E2LW6L-xhL-bMbe3VGSOgoykQT8DhOan0,12904
155
+ schemathesis-3.36.4.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
+ schemathesis-3.36.4.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
+ schemathesis-3.36.4.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
+ schemathesis-3.36.4.dist-info/RECORD,,