schemathesis 3.36.1__py3-none-any.whl → 3.36.3__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
 
@@ -239,6 +239,16 @@ http_interactions:"""
239
239
  thread_id: {item.thread_id}
240
240
  correlation_id: '{item.correlation_id}'
241
241
  data_generation_method: '{interaction.data_generation_method.value}'
242
+ meta:
243
+ description: """
244
+ )
245
+
246
+ if interaction.description is not None:
247
+ write_double_quoted(stream, interaction.description)
248
+ else:
249
+ stream.write("null")
250
+ stream.write(
251
+ f"""
242
252
  phase: {phase}
243
253
  elapsed: '{interaction.response.elapsed if interaction.response else 0}'
244
254
  recorded_at: '{interaction.recorded_at}'
@@ -15,7 +15,7 @@ from hypothesis_jsonschema._canonicalise import canonicalish
15
15
 
16
16
  from schemathesis.constants import NOT_SET
17
17
 
18
- from ._hypothesis import combine_strategies, get_single_example
18
+ from ._hypothesis import get_single_example
19
19
  from ._methods import DataGenerationMethod
20
20
 
21
21
  BUFFER_SIZE = 8 * 1024
@@ -36,16 +36,17 @@ UNKNOWN_PROPERTY_VALUE = 42
36
36
  class GeneratedValue:
37
37
  value: Any
38
38
  data_generation_method: DataGenerationMethod
39
+ description: str
39
40
 
40
- __slots__ = ("value", "data_generation_method")
41
+ __slots__ = ("value", "data_generation_method", "description")
41
42
 
42
43
  @classmethod
43
- def with_positive(cls, value: Any) -> GeneratedValue:
44
- return cls(value, DataGenerationMethod.positive)
44
+ def with_positive(cls, value: Any, *, description: str) -> GeneratedValue:
45
+ return cls(value=value, data_generation_method=DataGenerationMethod.positive, description=description)
45
46
 
46
47
  @classmethod
47
- def with_negative(cls, value: Any) -> GeneratedValue:
48
- return cls(value, DataGenerationMethod.negative)
48
+ def with_negative(cls, value: Any, *, description: str) -> GeneratedValue:
49
+ return cls(value=value, data_generation_method=DataGenerationMethod.negative, description=description)
49
50
 
50
51
 
51
52
  PositiveValue = GeneratedValue.with_positive
@@ -117,15 +118,15 @@ def _cover_positive_for_type(
117
118
  yield from cover_schema_iter(ctx, canonical)
118
119
  if enum is not NOT_SET:
119
120
  for value in enum:
120
- yield PositiveValue(value)
121
+ yield PositiveValue(value, description="Enum value")
121
122
  elif const is not NOT_SET:
122
- yield PositiveValue(const)
123
+ yield PositiveValue(const, description="Const value")
123
124
  elif ty is not None:
124
125
  if ty == "null":
125
- yield PositiveValue(None)
126
+ yield PositiveValue(None, description="Value null value")
126
127
  elif ty == "boolean":
127
- yield PositiveValue(True)
128
- yield PositiveValue(False)
128
+ yield PositiveValue(True, description="Valid boolean value")
129
+ yield PositiveValue(False, description="Valid boolean value")
129
130
  elif ty == "string":
130
131
  yield from _positive_string(ctx, schema)
131
132
  elif ty == "integer" or ty == "number":
@@ -157,7 +158,11 @@ def _ignore_unfixable(
157
158
  raise
158
159
 
159
160
 
160
- def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[GeneratedValue, None, None]:
161
+ def cover_schema_iter(
162
+ ctx: CoverageContext, schema: dict | bool, seen: set[Any | tuple[type, str]] | None = None
163
+ ) -> Generator[GeneratedValue, None, None]:
164
+ if seen is None:
165
+ seen = set()
161
166
  if isinstance(schema, bool):
162
167
  types = ["null", "boolean", "string", "number", "array", "object"]
163
168
  schema = {}
@@ -174,13 +179,16 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[Ge
174
179
  yield from _cover_positive_for_type(ctx, schema, ty)
175
180
  if DataGenerationMethod.negative in ctx.data_generation_methods:
176
181
  template = None
177
- seen: set[Any | tuple[type, str]] = set()
178
182
  for key, value in schema.items():
179
183
  with _ignore_unfixable():
180
184
  if key == "enum":
181
185
  yield from _negative_enum(ctx, value)
182
186
  elif key == "const":
183
- yield from _negative_enum(ctx, [value])
187
+ for value_ in _negative_enum(ctx, [value]):
188
+ k = _to_hashable_key(value_.value)
189
+ if k not in seen:
190
+ yield value_
191
+ seen.add(k)
184
192
  elif key == "type":
185
193
  yield from _negative_type(ctx, seen, value)
186
194
  elif key == "properties":
@@ -192,27 +200,39 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[Ge
192
200
  yield from _negative_format(ctx, schema, value)
193
201
  elif key == "maximum":
194
202
  next = value + 1
195
- yield NegativeValue(next)
196
- seen.add(next)
203
+ if next not in seen:
204
+ yield NegativeValue(next, description="Value greater than maximum")
205
+ seen.add(next)
197
206
  elif key == "minimum":
198
207
  next = value - 1
199
- yield NegativeValue(next)
200
- seen.add(next)
208
+ if next not in seen:
209
+ yield NegativeValue(next, description="Value smaller than minimum")
210
+ seen.add(next)
201
211
  elif key == "exclusiveMaximum" or key == "exclusiveMinimum" and value not in seen:
202
- yield NegativeValue(value)
212
+ verb = "greater" if key == "exclusiveMaximum" else "smaller"
213
+ limit = "maximum" if key == "exclusiveMaximum" else "minimum"
214
+ yield NegativeValue(value, description=f"Value {verb} than {limit}")
203
215
  seen.add(value)
204
216
  elif key == "multipleOf":
205
- yield from _negative_multiple_of(ctx, schema, value)
217
+ for value_ in _negative_multiple_of(ctx, schema, value):
218
+ k = _to_hashable_key(value_.value)
219
+ if k not in seen:
220
+ yield value_
221
+ seen.add(k)
206
222
  elif key == "minLength" and 0 < value < BUFFER_SIZE:
207
223
  with suppress(InvalidArgument):
208
- yield NegativeValue(
209
- ctx.generate_from_schema({**schema, "minLength": value - 1, "maxLength": value - 1})
210
- )
224
+ value = ctx.generate_from_schema({**schema, "minLength": value - 1, "maxLength": value - 1})
225
+ k = _to_hashable_key(value)
226
+ if k not in seen:
227
+ yield NegativeValue(value, description="String smaller than minLength")
228
+ seen.add(k)
211
229
  elif key == "maxLength" and value < BUFFER_SIZE:
212
230
  with suppress(InvalidArgument):
213
- yield NegativeValue(
214
- ctx.generate_from_schema({**schema, "minLength": value + 1, "maxLength": value + 1})
215
- )
231
+ value = ctx.generate_from_schema({**schema, "minLength": value + 1, "maxLength": value + 1})
232
+ k = _to_hashable_key(value)
233
+ if k not in seen:
234
+ yield NegativeValue(value, description="String larger than maxLength")
235
+ seen.add(k)
216
236
  elif key == "uniqueItems" and value:
217
237
  yield from _negative_unique_items(ctx, schema)
218
238
  elif key == "required":
@@ -220,20 +240,23 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[Ge
220
240
  yield from _negative_required(ctx, template, value)
221
241
  elif key == "additionalProperties" and not value:
222
242
  template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
223
- yield NegativeValue({**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE})
243
+ yield NegativeValue(
244
+ {**template, UNKNOWN_PROPERTY_KEY: UNKNOWN_PROPERTY_VALUE},
245
+ description="Object with unexpected properties",
246
+ )
224
247
  elif key == "allOf":
225
248
  nctx = ctx.with_negative()
226
249
  if len(value) == 1:
227
- yield from cover_schema_iter(nctx, value[0])
250
+ yield from cover_schema_iter(nctx, value[0], seen)
228
251
  else:
229
252
  with _ignore_unfixable():
230
253
  canonical = canonicalish(schema)
231
- yield from cover_schema_iter(nctx, canonical)
254
+ yield from cover_schema_iter(nctx, canonical, seen)
232
255
  elif key == "anyOf" or key == "oneOf":
233
256
  nctx = ctx.with_negative()
234
257
  # NOTE: Other sub-schemas are not filtered out
235
258
  for sub_schema in value:
236
- yield from cover_schema_iter(nctx, sub_schema)
259
+ yield from cover_schema_iter(nctx, sub_schema, seen)
237
260
 
238
261
 
239
262
  def _get_properties(schema: dict | bool) -> dict | bool:
@@ -274,43 +297,50 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
274
297
  default = schema.get("default")
275
298
  if example or examples or default:
276
299
  if example:
277
- yield PositiveValue(example)
300
+ yield PositiveValue(example, description="Example value")
278
301
  if examples:
279
302
  for example in examples:
280
- yield PositiveValue(example)
303
+ yield PositiveValue(example, description="Example value")
281
304
  if (
282
305
  default
283
306
  and not (example is not None and default == example)
284
307
  and not (examples is not None and any(default == ex for ex in examples))
285
308
  ):
286
- yield PositiveValue(default)
309
+ yield PositiveValue(default, description="Default value")
287
310
  elif not min_length and not max_length:
288
311
  # Default positive value
289
- yield PositiveValue(ctx.generate_from_schema(schema))
312
+ yield PositiveValue(ctx.generate_from_schema(schema), description="Valid string")
290
313
  elif "pattern" in schema:
291
314
  # Without merging `maxLength` & `minLength` into a regex it is problematic
292
315
  # to generate a valid value as the unredlying machinery will resort to filtering
293
316
  # and it is unlikely that it will generate a string of that length
294
- yield PositiveValue(ctx.generate_from_schema(schema))
317
+ yield PositiveValue(ctx.generate_from_schema(schema), description="Valid string")
295
318
  return
296
319
 
297
320
  seen = set()
298
321
 
299
322
  if min_length is not None and min_length < BUFFER_SIZE:
300
323
  # Exactly the minimum length
301
- yield PositiveValue(ctx.generate_from_schema({**schema, "maxLength": min_length}))
324
+ yield PositiveValue(
325
+ ctx.generate_from_schema({**schema, "maxLength": min_length}), description="Minimum length string"
326
+ )
302
327
  seen.add(min_length)
303
328
 
304
329
  # One character more than minimum if possible
305
330
  larger = min_length + 1
306
331
  if larger < BUFFER_SIZE and larger not in seen and (not max_length or larger <= max_length):
307
- yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}))
332
+ yield PositiveValue(
333
+ ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}),
334
+ description="Near-boundary length string",
335
+ )
308
336
  seen.add(larger)
309
337
 
310
338
  if max_length is not None:
311
339
  # Exactly the maximum length
312
340
  if max_length < BUFFER_SIZE and max_length not in seen:
313
- yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": max_length}))
341
+ yield PositiveValue(
342
+ ctx.generate_from_schema({**schema, "minLength": max_length}), description="Maximum length string"
343
+ )
314
344
  seen.add(max_length)
315
345
 
316
346
  # One character less than maximum if possible
@@ -320,7 +350,10 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
320
350
  and smaller not in seen
321
351
  and (smaller > 0 and (min_length is None or smaller >= min_length))
322
352
  ):
323
- yield PositiveValue(ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller}))
353
+ yield PositiveValue(
354
+ ctx.generate_from_schema({**schema, "minLength": smaller, "maxLength": smaller}),
355
+ description="Near-boundary length string",
356
+ )
324
357
  seen.add(smaller)
325
358
 
326
359
 
@@ -350,19 +383,19 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
350
383
 
351
384
  if example or examples or default:
352
385
  if example:
353
- yield PositiveValue(example)
386
+ yield PositiveValue(example, description="Example value")
354
387
  if examples:
355
388
  for example in examples:
356
- yield PositiveValue(example)
389
+ yield PositiveValue(example, description="Example value")
357
390
  if (
358
391
  default
359
392
  and not (example is not None and default == example)
360
393
  and not (examples is not None and any(default == ex for ex in examples))
361
394
  ):
362
- yield PositiveValue(default)
395
+ yield PositiveValue(default, description="Default value")
363
396
  elif not minimum and not maximum:
364
397
  # Default positive value
365
- yield PositiveValue(ctx.generate_from_schema(schema))
398
+ yield PositiveValue(ctx.generate_from_schema(schema), description="Valid number")
366
399
 
367
400
  seen = set()
368
401
 
@@ -373,7 +406,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
373
406
  else:
374
407
  smallest = minimum
375
408
  seen.add(smallest)
376
- yield PositiveValue(smallest)
409
+ yield PositiveValue(smallest, description="Minimum value")
377
410
 
378
411
  # One more than minimum if possible
379
412
  if multiple_of is not None:
@@ -382,7 +415,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
382
415
  larger = minimum + 1
383
416
  if larger not in seen and (not maximum or larger <= maximum):
384
417
  seen.add(larger)
385
- yield PositiveValue(larger)
418
+ yield PositiveValue(larger, description="Near-boundary number")
386
419
 
387
420
  if maximum is not None:
388
421
  # Exactly the maximum
@@ -392,7 +425,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
392
425
  largest = maximum
393
426
  if largest not in seen:
394
427
  seen.add(largest)
395
- yield PositiveValue(largest)
428
+ yield PositiveValue(largest, description="Maximum value")
396
429
 
397
430
  # One less than maximum if possible
398
431
  if multiple_of is not None:
@@ -401,7 +434,7 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
401
434
  smaller = maximum - 1
402
435
  if smaller not in seen and (smaller > 0 and (minimum is None or smaller >= minimum)):
403
436
  seen.add(smaller)
404
- yield PositiveValue(smaller)
437
+ yield PositiveValue(smaller, description="Near-boundary number")
405
438
 
406
439
 
407
440
  def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Generator[GeneratedValue, None, None]:
@@ -412,18 +445,18 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
412
445
 
413
446
  if example or examples or default:
414
447
  if example:
415
- yield PositiveValue(example)
448
+ yield PositiveValue(example, description="Example value")
416
449
  if examples:
417
450
  for example in examples:
418
- yield PositiveValue(example)
451
+ yield PositiveValue(example, description="Example value")
419
452
  if (
420
453
  default
421
454
  and not (example is not None and default == example)
422
455
  and not (examples is not None and any(default == ex for ex in examples))
423
456
  ):
424
- yield PositiveValue(default)
457
+ yield PositiveValue(default, description="Default value")
425
458
  else:
426
- yield PositiveValue(template)
459
+ yield PositiveValue(template, description="Valid array")
427
460
  seen.add(len(template))
428
461
 
429
462
  # Boundary and near-boundary sizes
@@ -435,12 +468,18 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
435
468
  # One item more than minimum if possible
436
469
  larger = min_items + 1
437
470
  if larger not in seen and (max_items is None or larger <= max_items):
438
- yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger}))
471
+ yield PositiveValue(
472
+ ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger}),
473
+ description="Near-boundary items array",
474
+ )
439
475
  seen.add(larger)
440
476
 
441
477
  if max_items is not None:
442
478
  if max_items < BUFFER_SIZE and max_items not in seen:
443
- yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": max_items}))
479
+ yield PositiveValue(
480
+ ctx.generate_from_schema({**schema, "minItems": max_items}),
481
+ description="Maximum items array",
482
+ )
444
483
  seen.add(max_items)
445
484
 
446
485
  # One item smaller than maximum if possible
@@ -451,7 +490,10 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
451
490
  and smaller not in seen
452
491
  and (min_items is None or smaller >= min_items)
453
492
  ):
454
- yield PositiveValue(ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller}))
493
+ yield PositiveValue(
494
+ ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller}),
495
+ description="Near-boundary items array",
496
+ )
455
497
  seen.add(smaller)
456
498
 
457
499
 
@@ -462,19 +504,18 @@ def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Gene
462
504
 
463
505
  if example or examples or default:
464
506
  if example:
465
- yield PositiveValue(example)
507
+ yield PositiveValue(example, description="Example value")
466
508
  if examples:
467
509
  for example in examples:
468
- yield PositiveValue(example)
510
+ yield PositiveValue(example, description="Example value")
469
511
  if (
470
512
  default
471
513
  and not (example is not None and default == example)
472
514
  and not (examples is not None and any(default == ex for ex in examples))
473
515
  ):
474
- yield PositiveValue(default)
475
-
516
+ yield PositiveValue(default, description="Default value")
476
517
  else:
477
- yield PositiveValue(template)
518
+ yield PositiveValue(template, description="Valid object")
478
519
 
479
520
  properties = schema.get("properties", {})
480
521
  required = set(schema.get("required", []))
@@ -485,22 +526,24 @@ def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Gene
485
526
  for name in optional:
486
527
  combo = {k: v for k, v in template.items() if k in required or k == name}
487
528
  if combo != template:
488
- yield PositiveValue(combo)
529
+ yield PositiveValue(combo, description=f"Object with all required properties and '{name}'")
489
530
  # Generate one combination for each size from 2 to N-1
490
531
  for selection in select_combinations(optional):
491
532
  combo = {k: v for k, v in template.items() if k in required or k in selection}
492
- yield PositiveValue(combo)
533
+ yield PositiveValue(combo, description="Object with all required and a subset of optional properties")
493
534
  # Generate only required properties
494
535
  if set(properties) != required:
495
536
  only_required = {k: v for k, v in template.items() if k in required}
496
- yield PositiveValue(only_required)
537
+ yield PositiveValue(only_required, description="Object with only required properties")
497
538
  seen = set()
498
539
  for name, sub_schema in properties.items():
499
540
  seen.add(_to_hashable_key(template.get(name)))
500
541
  for new in cover_schema_iter(ctx, sub_schema):
501
542
  key = _to_hashable_key(new.value)
502
543
  if key not in seen:
503
- yield PositiveValue({**template, name: new.value})
544
+ yield PositiveValue(
545
+ {**template, name: new.value}, description=f"Object with valid '{name}' value: {new.description}"
546
+ )
504
547
  seen.add(key)
505
548
  seen.clear()
506
549
 
@@ -513,7 +556,7 @@ def select_combinations(optional: list[str]) -> Iterator[tuple[str, ...]]:
513
556
  def _negative_enum(ctx: CoverageContext, value: list) -> Generator[GeneratedValue, None, None]:
514
557
  strategy = JSON_STRATEGY.filter(lambda x: x not in value)
515
558
  # The exact negative value is not important here
516
- yield NegativeValue(ctx.generate_from(strategy, cached=True))
559
+ yield NegativeValue(ctx.generate_from(strategy, cached=True), description="Invalid enum value")
517
560
 
518
561
 
519
562
  def _negative_properties(
@@ -522,11 +565,17 @@ def _negative_properties(
522
565
  nctx = ctx.with_negative()
523
566
  for key, sub_schema in properties.items():
524
567
  for value in cover_schema_iter(nctx, sub_schema):
525
- yield NegativeValue({**template, key: value.value})
568
+ yield NegativeValue(
569
+ {**template, key: value.value},
570
+ description=f"Object with invalid '{key}' value: {value.description}",
571
+ )
526
572
 
527
573
 
528
574
  def _negative_pattern(ctx: CoverageContext, pattern: str) -> Generator[GeneratedValue, None, None]:
529
- yield NegativeValue(ctx.generate_from(st.text().filter(lambda x: x != pattern), cached=True))
575
+ yield NegativeValue(
576
+ ctx.generate_from(st.text().filter(lambda x: x != pattern), cached=True),
577
+ description=f"Value not matching the '{pattern}' pattern",
578
+ )
530
579
 
531
580
 
532
581
  def _with_negated_key(schema: dict, key: str, value: Any) -> dict:
@@ -536,19 +585,25 @@ def _with_negated_key(schema: dict, key: str, value: Any) -> dict:
536
585
  def _negative_multiple_of(
537
586
  ctx: CoverageContext, schema: dict, multiple_of: int | float
538
587
  ) -> Generator[GeneratedValue, None, None]:
539
- yield NegativeValue(ctx.generate_from_schema(_with_negated_key(schema, "multipleOf", multiple_of)))
588
+ yield NegativeValue(
589
+ ctx.generate_from_schema(_with_negated_key(schema, "multipleOf", multiple_of)),
590
+ description=f"Non-multiple of {multiple_of}",
591
+ )
540
592
 
541
593
 
542
594
  def _negative_unique_items(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
543
595
  unique = ctx.generate_from_schema({**schema, "type": "array", "minItems": 1, "maxItems": 1})
544
- yield NegativeValue(unique + unique)
596
+ yield NegativeValue(unique + unique, description="Non-unique items")
545
597
 
546
598
 
547
599
  def _negative_required(
548
600
  ctx: CoverageContext, template: dict, required: list[str]
549
601
  ) -> Generator[GeneratedValue, None, None]:
550
602
  for key in required:
551
- yield NegativeValue({k: v for k, v in template.items() if k != key})
603
+ yield NegativeValue(
604
+ {k: v for k, v in template.items() if k != key},
605
+ description=f"Missing required property: {key}",
606
+ )
552
607
 
553
608
 
554
609
  def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generator[GeneratedValue, None, None]:
@@ -561,7 +616,7 @@ def _negative_format(ctx: CoverageContext, schema: dict, format: str) -> Generat
561
616
  lambda v: (format == "hostname" and v == "")
562
617
  or not jsonschema.Draft202012Validator.FORMAT_CHECKER.conforms(v, format)
563
618
  )
564
- yield NegativeValue(ctx.generate_from(strategy))
619
+ yield NegativeValue(ctx.generate_from(strategy), description=f"Value not matching the '{format}' format")
565
620
 
566
621
 
567
622
  def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Generator[GeneratedValue, None, None]:
@@ -584,10 +639,13 @@ def _negative_type(ctx: CoverageContext, seen: set, ty: str | list[str]) -> Gene
584
639
  del strategies["integer"]
585
640
  if "integer" in types:
586
641
  strategies["number"] = FLOAT_STRATEGY.filter(lambda x: x != int(x))
587
- negative_strategy = combine_strategies(tuple(strategies.values())).filter(lambda x: _to_hashable_key(x) not in seen)
588
- value = ctx.generate_from(negative_strategy, cached=True)
589
- yield NegativeValue(value)
590
- seen.add(_to_hashable_key(value))
642
+ for strat in strategies.values():
643
+ value = ctx.generate_from(strat, cached=True)
644
+ hashed = _to_hashable_key(value)
645
+ if hashed in seen:
646
+ continue
647
+ yield NegativeValue(value, description="Incorrect type")
648
+ seen.add(hashed)
591
649
 
592
650
 
593
651
  def push_examples_to_properties(schema: dict[str, Any]) -> None:
@@ -595,9 +653,10 @@ def push_examples_to_properties(schema: dict[str, Any]) -> None:
595
653
  if "examples" in schema and "properties" in schema:
596
654
  properties = schema["properties"]
597
655
  for example in schema["examples"]:
598
- for prop, value in example.items():
599
- if prop in properties:
600
- if "examples" not in properties[prop]:
601
- properties[prop]["examples"] = []
602
- if value not in schema["properties"][prop]["examples"]:
603
- properties[prop]["examples"].append(value)
656
+ if isinstance(example, dict):
657
+ for prop, value in example.items():
658
+ if prop in properties:
659
+ if "examples" not in properties[prop]:
660
+ properties[prop]["examples"] = []
661
+ if value not in schema["properties"][prop]["examples"]:
662
+ properties[prop]["examples"].append(value)
@@ -6,10 +6,11 @@ from dataclasses import dataclass
6
6
  from typing import TYPE_CHECKING, Callable, Optional
7
7
 
8
8
  if TYPE_CHECKING:
9
- from ..types import RawAuth
9
+ from requests.structures import CaseInsensitiveDict
10
+
10
11
  from ..models import Case
11
12
  from ..transports.responses import GenericResponse
12
- from requests.structures import CaseInsensitiveDict
13
+ from ..types import RawAuth
13
14
 
14
15
 
15
16
  CheckFunction = Callable[["CheckContext", "GenericResponse", "Case"], Optional[bool]]
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(
@@ -527,7 +528,12 @@ def is_valid_path(parameters: dict[str, Any]) -> bool:
527
528
  disallowed_values = (SLASH, "")
528
529
 
529
530
  return not any(
530
- (value in disallowed_values or is_illegal_surrogate(value) or isinstance(value, str) and SLASH in value)
531
+ (
532
+ value in disallowed_values
533
+ or is_illegal_surrogate(value)
534
+ or isinstance(value, str)
535
+ and (SLASH in value or "}" in value or "{" in value)
536
+ )
531
537
  for value in parameters.values()
532
538
  )
533
539
 
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
4
3
  import enum
4
+ from dataclasses import dataclass
5
5
  from http.cookies import SimpleCookie
6
6
  from typing import TYPE_CHECKING, Any, Dict, Generator, NoReturn, cast
7
7
  from urllib.parse import parse_qs, urlparse
@@ -5,6 +5,7 @@ from typing import Any, Callable
5
5
 
6
6
  from ...internal.copy import fast_deepcopy
7
7
  from ...internal.jsonschema import traverse_schema
8
+ from .patterns import update_quantifier
8
9
 
9
10
 
10
11
  def to_json_schema(
@@ -24,6 +25,15 @@ def to_json_schema(
24
25
  if schema_type == "file":
25
26
  schema["type"] = "string"
26
27
  schema["format"] = "binary"
28
+ pattern = schema.get("pattern")
29
+ min_length = schema.get("minLength")
30
+ max_length = schema.get("maxLength")
31
+ if pattern and (min_length or max_length):
32
+ new_pattern = update_quantifier(pattern, min_length, max_length)
33
+ if new_pattern != pattern:
34
+ schema.pop("minLength", None)
35
+ schema.pop("maxLength", None)
36
+ schema["pattern"] = new_pattern
27
37
  if schema_type == "object":
28
38
  if is_response_schema:
29
39
  # Write-only properties should not occur in responses
@@ -0,0 +1,120 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from functools import lru_cache
5
+
6
+ try: # pragma: no cover
7
+ import re._constants as sre
8
+ import re._parser as sre_parse
9
+ except ImportError:
10
+ import sre_constants as sre
11
+ import sre_parse
12
+
13
+ ANCHOR = sre.AT
14
+ REPEATS: tuple
15
+ if hasattr(sre, "POSSESSIVE_REPEAT"):
16
+ REPEATS = (sre.MIN_REPEAT, sre.MAX_REPEAT, sre.POSSESSIVE_REPEAT)
17
+ else:
18
+ REPEATS = (sre.MIN_REPEAT, sre.MAX_REPEAT)
19
+ LITERAL = sre.LITERAL
20
+ IN = sre.IN
21
+ MAXREPEAT = sre_parse.MAXREPEAT
22
+
23
+
24
+ @lru_cache()
25
+ def update_quantifier(pattern: str, min_length: int | None, max_length: int | None) -> str:
26
+ """Update the quantifier of a regular expression based on given min and max lengths."""
27
+ if not pattern or (min_length in (None, 0) and max_length is None):
28
+ return pattern
29
+
30
+ try:
31
+ parsed = sre_parse.parse(pattern)
32
+ return _handle_parsed_pattern(parsed, pattern, min_length, max_length)
33
+ except re.error:
34
+ # Invalid pattern
35
+ return pattern
36
+
37
+
38
+ def _handle_parsed_pattern(parsed: list, pattern: str, min_length: int | None, max_length: int | None) -> str:
39
+ """Handle the parsed pattern and update quantifiers based on different cases."""
40
+ if len(parsed) == 1:
41
+ op, value = parsed[0]
42
+ return _update_quantifier(op, value, pattern, min_length, max_length)
43
+ elif len(parsed) == 2:
44
+ if parsed[0][0] == ANCHOR:
45
+ # Starts with an anchor
46
+ op, value = parsed[1]
47
+ leading_anchor = pattern[0]
48
+ return leading_anchor + _update_quantifier(op, value, pattern[1:], min_length, max_length)
49
+ if parsed[1][0] == ANCHOR:
50
+ # Ends with an anchor
51
+ op, value = parsed[0]
52
+ trailing_anchor = pattern[-1]
53
+ return _update_quantifier(op, value, pattern[:-1], min_length, max_length) + trailing_anchor
54
+ elif len(parsed) == 3 and parsed[0][0] == ANCHOR and parsed[2][0] == ANCHOR:
55
+ op, value = parsed[1]
56
+ leading_anchor = pattern[0]
57
+ trailing_anchor = pattern[-1]
58
+ return leading_anchor + _update_quantifier(op, value, pattern[1:-1], min_length, max_length) + trailing_anchor
59
+ return pattern
60
+
61
+
62
+ def _update_quantifier(op: int, value: tuple, pattern: str, min_length: int | None, max_length: int | None) -> str:
63
+ """Update the quantifier based on the operation type and given constraints."""
64
+ if op in REPEATS:
65
+ return _handle_repeat_quantifier(value, pattern, min_length, max_length)
66
+ if op in (LITERAL, IN) and max_length != 0:
67
+ return _handle_literal_or_in_quantifier(pattern, min_length, max_length)
68
+ return pattern
69
+
70
+
71
+ def _handle_repeat_quantifier(
72
+ value: tuple[int, int, tuple], pattern: str, min_length: int | None, max_length: int | None
73
+ ) -> str:
74
+ """Handle repeat quantifiers (e.g., '+', '*', '?')."""
75
+ min_repeat, max_repeat, _ = value
76
+ min_length, max_length = _build_size(min_repeat, max_repeat, min_length, max_length)
77
+ if min_length > max_length:
78
+ return pattern
79
+ return f"({_strip_quantifier(pattern)})" + _build_quantifier(min_length, max_length)
80
+
81
+
82
+ def _handle_literal_or_in_quantifier(pattern: str, min_length: int | None, max_length: int | None) -> str:
83
+ """Handle literal or character class quantifiers."""
84
+ min_length = 1 if min_length is None else max(min_length, 1)
85
+ return f"({pattern})" + _build_quantifier(min_length, max_length)
86
+
87
+
88
+ def _build_quantifier(minimum: int | None, maximum: int | None) -> str:
89
+ """Construct a quantifier string based on min and max values."""
90
+ if maximum == MAXREPEAT or maximum is None:
91
+ return f"{{{minimum or 0},}}"
92
+ if minimum == maximum:
93
+ return f"{{{minimum}}}"
94
+ return f"{{{minimum or 0},{maximum}}}"
95
+
96
+
97
+ def _build_size(min_repeat: int, max_repeat: int, min_length: int | None, max_length: int | None) -> tuple[int, int]:
98
+ """Merge the current repetition constraints with the provided min and max lengths."""
99
+ if min_length is not None:
100
+ min_repeat = max(min_repeat, min_length)
101
+ if max_length is not None:
102
+ if max_repeat == MAXREPEAT:
103
+ max_repeat = max_length
104
+ else:
105
+ max_repeat = min(max_repeat, max_length)
106
+ return min_repeat, max_repeat
107
+
108
+
109
+ def _strip_quantifier(pattern: str) -> str:
110
+ """Remove quantifier from the pattern."""
111
+ # Lazy & posessive quantifiers
112
+ if pattern.endswith(("*?", "+?", "??", "*+", "?+", "++")):
113
+ return pattern[:-2]
114
+ if pattern.endswith(("?", "*", "+")):
115
+ pattern = pattern[:-1]
116
+ if pattern.endswith("}"):
117
+ # Find the start of the exact quantifier and drop everything since that index
118
+ idx = pattern.rfind("{")
119
+ pattern = pattern[:idx]
120
+ return pattern
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: schemathesis
3
- Version: 3.36.1
3
+ Version: 3.36.3
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=uzU51QMqUWhkxdX4H9bavVjeTIWX5_i-E3CnOK2mAU4,19698
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
@@ -60,9 +60,9 @@ schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZE
60
60
  schemathesis/generation/__init__.py,sha256=29Zys_tD6kfngaC4zHeC6TOBZQcmo7CWm7KDSYsHStQ,1581
61
61
  schemathesis/generation/_hypothesis.py,sha256=QDBzpcM9eXPgLGGdCPdGlxCtfMXD4YBN9_6Oz73lofI,1406
62
62
  schemathesis/generation/_methods.py,sha256=jCK09f4sedDfePrS-6BIiE-CcEE8fJ4ZHxq1BHoTltQ,1101
63
- schemathesis/generation/coverage.py,sha256=gEB8J9qJTSVRdINVYUw0twCxPIwmuWF2FPJRIwSiy5A,23944
63
+ schemathesis/generation/coverage.py,sha256=5NyDjceKqmQVmDn4xwOnS61xRpS2Z_YcbtjNuveI0RA,27582
64
64
  schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
65
- schemathesis/internal/checks.py,sha256=_DO0SlpfgiwEu5vkfS3hyXyC7BsGKKIPuAPgLhfGZ1M,1667
65
+ schemathesis/internal/checks.py,sha256=m6lY6x2Pkz6AjU8Hs-UMSDJZEOq8EcgZOCp6BQA7p_g,1668
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,10 +104,10 @@ 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=XgKq36ONJIWM-8ASnDpzOgcCcVz-uUQw74bOxcUC3n8,24201
108
- schemathesis/specs/openapi/checks.py,sha256=sOfnEoeu7aGTM7RXplhPy1iUaibxIzytjWtBp4at8S4,22288
107
+ schemathesis/specs/openapi/_hypothesis.py,sha256=Ym1d3GXlabOSbDk_AEkmkZGl9EMIDpumciLZtfYNdUI,24323
108
+ schemathesis/specs/openapi/checks.py,sha256=-4qOzkova0e4QSqdgsoUiOv2bg57HZmzbpAiAeotc3Q,22288
109
109
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
110
- schemathesis/specs/openapi/converter.py,sha256=TaYgc5BBHPdkN-n0lqpbeVgLu3eL3L8Wu3y_Vo3TJaQ,2800
110
+ schemathesis/specs/openapi/converter.py,sha256=NkrzBNjtmVwQTeE73NOtwB_puvQTjxxqqrc7gD_yscc,3241
111
111
  schemathesis/specs/openapi/definitions.py,sha256=nEsCKn_LgqYjZ9nNWp-8KUIrB4S94pT3GsV5A8UIzDw,94043
112
112
  schemathesis/specs/openapi/examples.py,sha256=FwhPWca7bpdHpUp_LRoK09DVgusojO3aXXhXYrK373I,20354
113
113
  schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
@@ -115,6 +115,7 @@ schemathesis/specs/openapi/links.py,sha256=a8JmWM9aZhrR5CfyIh6t2SkfonMLfYKOScXY2
115
115
  schemathesis/specs/openapi/loaders.py,sha256=5B1cgYEBj3h2psPQxzrQ5Xq5owLVGw-u9HsCQIx7yFE,25705
116
116
  schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
117
117
  schemathesis/specs/openapi/parameters.py,sha256=CqJdS4d14l25_yEbqkLCnfIdDTlodRhJpxD8EXdaFwM,14059
118
+ schemathesis/specs/openapi/patterns.py,sha256=IK2BkXI1xByEz5if6jvydFE07nq5rDa4k_-2xX7ifG8,4715
118
119
  schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
119
120
  schemathesis/specs/openapi/schemas.py,sha256=t3Gz2q-d9b8Oy-hDhz0rNfjYT3Nx-uOeLOmjO9hpRM0,53741
120
121
  schemathesis/specs/openapi/security.py,sha256=Z-6pk2Ga1PTUtBe298KunjVHsNh5A-teegeso7zcPIE,7138
@@ -150,8 +151,8 @@ schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZ
150
151
  schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
151
152
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
152
153
  schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
153
- schemathesis-3.36.1.dist-info/METADATA,sha256=ysyAwX1ruHhcAtdGT2Ty2lpc4UpM0zyotuwzDprQd08,12904
154
- schemathesis-3.36.1.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
155
- schemathesis-3.36.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
156
- schemathesis-3.36.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
157
- schemathesis-3.36.1.dist-info/RECORD,,
154
+ schemathesis-3.36.3.dist-info/METADATA,sha256=7TD5mpHwI_7wHLTg2EfyokGGn12X5d-gT6Wfc08-iEI,12904
155
+ schemathesis-3.36.3.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
156
+ schemathesis-3.36.3.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
157
+ schemathesis-3.36.3.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
158
+ schemathesis-3.36.3.dist-info/RECORD,,