schemathesis 4.0.0a8__py3-none-any.whl → 4.0.0a10__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.
@@ -23,7 +23,7 @@ class JunitXMLHandler(EventHandler):
23
23
  label = event.recorder.label
24
24
  test_case = self.get_or_create_test_case(label)
25
25
  test_case.elapsed_sec += event.elapsed_time
26
- if event.status == Status.FAILURE:
26
+ if event.status == Status.FAILURE and label in ctx.statistic.failures:
27
27
  add_failure(test_case, ctx.statistic.failures[label].values(), ctx)
28
28
  elif event.status == Status.SKIP and event.skip_reason is not None:
29
29
  test_case.add_skipped_info(output=event.skip_reason)
@@ -4,6 +4,7 @@ import enum
4
4
  from dataclasses import dataclass
5
5
 
6
6
  SCHEMATHESIS_TEST_CASE_HEADER = "X-Schemathesis-TestCaseId"
7
+ INTERNAL_BUFFER_SIZE = 32 * 1024
7
8
 
8
9
 
9
10
  class NotSet: ...
@@ -16,7 +16,7 @@ from hypothesis_jsonschema import from_schema
16
16
  from hypothesis_jsonschema._canonicalise import canonicalish
17
17
  from hypothesis_jsonschema._from_schema import STRING_FORMATS as BUILT_IN_STRING_FORMATS
18
18
 
19
- from schemathesis.core import NOT_SET
19
+ from schemathesis.core import INTERNAL_BUFFER_SIZE, NOT_SET
20
20
  from schemathesis.core.compat import RefResolutionError
21
21
  from schemathesis.core.transforms import deepclone
22
22
  from schemathesis.core.validation import has_invalid_characters, is_latin_1_encodable
@@ -36,7 +36,8 @@ def json_recursive_strategy(strategy: st.SearchStrategy) -> st.SearchStrategy:
36
36
  return st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3)
37
37
 
38
38
 
39
- BUFFER_SIZE = 8 * 1024
39
+ NEGATIVE_MODE_MAX_LENGTH_WITH_PATTERN = 100
40
+ NEGATIVE_MODE_MAX_ITEMS = 15
40
41
  FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(_replace_zero_with_nonzero)
41
42
  NUMERIC_STRATEGY: st.SearchStrategy = st.integers() | FLOAT_STRATEGY
42
43
  JSON_STRATEGY: st.SearchStrategy = st.recursive(
@@ -159,7 +160,7 @@ class CoverageContext:
159
160
  def generate_from_schema(self, schema: dict | bool) -> Any:
160
161
  if isinstance(schema, bool):
161
162
  return 0
162
- keys = sorted([k for k in schema if not k.startswith("x-") and k not in ["description", "example"]])
163
+ keys = sorted([k for k in schema if not k.startswith("x-") and k not in ["description", "example", "examples"]])
163
164
  if keys == ["type"] and isinstance(schema["type"], str) and schema["type"] in STRATEGIES_FOR_TYPE:
164
165
  return cached_draw(STRATEGIES_FOR_TYPE[schema["type"]])
165
166
  if keys == ["format", "type"]:
@@ -257,6 +258,9 @@ def _cover_positive_for_type(
257
258
  if ty == "object" or ty == "array":
258
259
  template_schema = _get_template_schema(schema, ty)
259
260
  template = ctx.generate_from_schema(template_schema)
261
+ elif "properties" in schema or "required" in schema:
262
+ template_schema = _get_template_schema(schema, "object")
263
+ template = ctx.generate_from_schema(template_schema)
260
264
  else:
261
265
  template = None
262
266
  if GenerationMode.POSITIVE in ctx.generation_modes:
@@ -295,6 +299,8 @@ def _cover_positive_for_type(
295
299
  yield from _positive_array(ctx, schema, cast(list, template))
296
300
  elif ty == "object":
297
301
  yield from _positive_object(ctx, schema, cast(dict, template))
302
+ elif "properties" in schema or "required" in schema:
303
+ yield from _positive_object(ctx, schema, cast(dict, template))
298
304
 
299
305
 
300
306
  @contextmanager
@@ -386,42 +392,59 @@ def cover_schema_iter(
386
392
  if k not in seen:
387
393
  yield value_
388
394
  seen.add(k)
389
- elif key == "minLength" and 0 < value < BUFFER_SIZE:
390
- with suppress(InvalidArgument):
391
- min_length = max_length = value - 1
392
- new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
393
- new_schema.setdefault("type", "string")
394
- if "pattern" in new_schema:
395
- new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
396
- if new_schema["pattern"] == schema["pattern"]:
397
- # Pattern wasn't updated, try to generate a valid value then shrink the string to the required length
398
- del new_schema["minLength"]
399
- del new_schema["maxLength"]
400
- value = ctx.generate_from_schema(new_schema)[:max_length]
401
- else:
402
- value = ctx.generate_from_schema(new_schema)
403
- else:
404
- value = ctx.generate_from_schema(new_schema)
395
+ elif key == "minLength" and 0 < value < INTERNAL_BUFFER_SIZE:
396
+ if value == 1:
397
+ # In this case, the only possible negative string is an empty one
398
+ # The `pattern` value may require an non-empty one and the generation will fail
399
+ # However, it is fine to violate `pattern` here as it is negative string generation anyway
400
+ value = ""
405
401
  k = _to_hashable_key(value)
406
402
  if k not in seen:
407
403
  yield NegativeValue(
408
404
  value, description="String smaller than minLength", location=ctx.current_path
409
405
  )
410
406
  seen.add(k)
411
- elif key == "maxLength" and value < BUFFER_SIZE:
407
+ else:
408
+ with suppress(InvalidArgument):
409
+ min_length = max_length = value - 1
410
+ new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
411
+ new_schema.setdefault("type", "string")
412
+ if "pattern" in new_schema:
413
+ new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
414
+ if new_schema["pattern"] == schema["pattern"]:
415
+ # Pattern wasn't updated, try to generate a valid value then shrink the string to the required length
416
+ del new_schema["minLength"]
417
+ del new_schema["maxLength"]
418
+ value = ctx.generate_from_schema(new_schema)[:max_length]
419
+ else:
420
+ value = ctx.generate_from_schema(new_schema)
421
+ else:
422
+ value = ctx.generate_from_schema(new_schema)
423
+ k = _to_hashable_key(value)
424
+ if k not in seen:
425
+ yield NegativeValue(
426
+ value, description="String smaller than minLength", location=ctx.current_path
427
+ )
428
+ seen.add(k)
429
+ elif key == "maxLength" and value < INTERNAL_BUFFER_SIZE:
412
430
  try:
413
431
  min_length = max_length = value + 1
414
432
  new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
415
433
  new_schema.setdefault("type", "string")
416
434
  if "pattern" in new_schema:
417
- new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
418
- if new_schema["pattern"] == schema["pattern"]:
419
- # Pattern wasn't updated, try to generate a valid value then extend the string to the required length
420
- del new_schema["minLength"]
421
- del new_schema["maxLength"]
422
- value = ctx.generate_from_schema(new_schema).ljust(max_length, "0")
423
- else:
435
+ if value > NEGATIVE_MODE_MAX_LENGTH_WITH_PATTERN:
436
+ # Large `maxLength` value can be extremely slow to generate when combined with `pattern`
437
+ del new_schema["pattern"]
424
438
  value = ctx.generate_from_schema(new_schema)
439
+ else:
440
+ new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
441
+ if new_schema["pattern"] == schema["pattern"]:
442
+ # Pattern wasn't updated, try to generate a valid value then extend the string to the required length
443
+ del new_schema["minLength"]
444
+ del new_schema["maxLength"]
445
+ value = ctx.generate_from_schema(new_schema).ljust(max_length, "0")
446
+ else:
447
+ value = ctx.generate_from_schema(new_schema)
425
448
  else:
426
449
  value = ctx.generate_from_schema(new_schema)
427
450
  k = _to_hashable_key(value)
@@ -437,11 +460,34 @@ def cover_schema_iter(
437
460
  elif key == "required":
438
461
  template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
439
462
  yield from _negative_required(ctx, template, value)
440
- elif key == "maxItems" and isinstance(value, int) and value < BUFFER_SIZE:
441
- try:
442
- # Force the array to have one more item than allowed
443
- new_schema = {**schema, "minItems": value + 1, "maxItems": value + 1, "type": "array"}
444
- array_value = ctx.generate_from_schema(new_schema)
463
+ elif key == "maxItems" and isinstance(value, int) and value < INTERNAL_BUFFER_SIZE:
464
+ if value > NEGATIVE_MODE_MAX_ITEMS:
465
+ # It could be extremely slow to generate large arrays
466
+ # Generate values up to the limit and reuse them to construct the final array
467
+ new_schema = {
468
+ **schema,
469
+ "minItems": NEGATIVE_MODE_MAX_ITEMS,
470
+ "maxItems": NEGATIVE_MODE_MAX_ITEMS,
471
+ "type": "array",
472
+ }
473
+ if "items" in schema and isinstance(schema["items"], dict):
474
+ # The schema may have another large array nested, therefore generate covering cases
475
+ # and use them to build an array for the current schema
476
+ negative = [case.value for case in cover_schema_iter(ctx, schema["items"])]
477
+ positive = [case.value for case in cover_schema_iter(ctx.with_positive(), schema["items"])]
478
+ # Interleave positive & negative values
479
+ array_value = [value for pair in zip(positive, negative) for value in pair][
480
+ :NEGATIVE_MODE_MAX_ITEMS
481
+ ]
482
+ else:
483
+ array_value = ctx.generate_from_schema(new_schema)
484
+
485
+ # Extend the array to be of length value + 1 by repeating its own elements
486
+ diff = value + 1 - len(array_value)
487
+ if diff > 0:
488
+ array_value += (
489
+ array_value * (diff // len(array_value)) + array_value[: diff % len(array_value)]
490
+ )
445
491
  k = _to_hashable_key(array_value)
446
492
  if k not in seen:
447
493
  yield NegativeValue(
@@ -450,8 +496,21 @@ def cover_schema_iter(
450
496
  location=ctx.current_path,
451
497
  )
452
498
  seen.add(k)
453
- except (InvalidArgument, Unsatisfiable):
454
- pass
499
+ else:
500
+ try:
501
+ # Force the array to have one more item than allowed
502
+ new_schema = {**schema, "minItems": value + 1, "maxItems": value + 1, "type": "array"}
503
+ array_value = ctx.generate_from_schema(new_schema)
504
+ k = _to_hashable_key(array_value)
505
+ if k not in seen:
506
+ yield NegativeValue(
507
+ array_value,
508
+ description="Array with more items than allowed by maxItems",
509
+ location=ctx.current_path,
510
+ )
511
+ seen.add(k)
512
+ except (InvalidArgument, Unsatisfiable):
513
+ pass
455
514
  elif key == "minItems" and isinstance(value, int) and value > 0:
456
515
  try:
457
516
  # Force the array to have one less item than the minimum
@@ -557,7 +616,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
557
616
 
558
617
  seen = set()
559
618
 
560
- if min_length is not None and min_length < BUFFER_SIZE:
619
+ if min_length is not None and min_length < INTERNAL_BUFFER_SIZE:
561
620
  # Exactly the minimum length
562
621
  yield PositiveValue(
563
622
  ctx.generate_from_schema({**schema, "maxLength": min_length}), description="Minimum length string"
@@ -566,7 +625,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
566
625
 
567
626
  # One character more than minimum if possible
568
627
  larger = min_length + 1
569
- if larger < BUFFER_SIZE and larger not in seen and (not max_length or larger <= max_length):
628
+ if larger < INTERNAL_BUFFER_SIZE and larger not in seen and (not max_length or larger <= max_length):
570
629
  yield PositiveValue(
571
630
  ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}),
572
631
  description="Near-boundary length string",
@@ -575,7 +634,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
575
634
 
576
635
  if max_length is not None:
577
636
  # Exactly the maximum length
578
- if max_length < BUFFER_SIZE and max_length not in seen:
637
+ if max_length < INTERNAL_BUFFER_SIZE and max_length not in seen:
579
638
  yield PositiveValue(
580
639
  ctx.generate_from_schema({**schema, "minLength": max_length}), description="Maximum length string"
581
640
  )
@@ -584,7 +643,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
584
643
  # One character less than maximum if possible
585
644
  smaller = max_length - 1
586
645
  if (
587
- smaller < BUFFER_SIZE
646
+ smaller < INTERNAL_BUFFER_SIZE
588
647
  and smaller not in seen
589
648
  and (smaller > 0 and (min_length is None or smaller >= min_length))
590
649
  ):
@@ -683,19 +742,22 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
683
742
 
684
743
  if example or examples or default:
685
744
  if example:
745
+ seen.add(_to_hashable_key(example))
686
746
  yield PositiveValue(example, description="Example value")
687
747
  if examples:
688
748
  for example in examples:
749
+ seen.add(_to_hashable_key(example))
689
750
  yield PositiveValue(example, description="Example value")
690
751
  if (
691
752
  default
692
753
  and not (example is not None and default == example)
693
754
  and not (examples is not None and any(default == ex for ex in examples))
694
755
  ):
756
+ seen.add(_to_hashable_key(default))
695
757
  yield PositiveValue(default, description="Default value")
696
758
  else:
697
759
  yield PositiveValue(template, description="Valid array")
698
- seen.add(len(template))
760
+ seen.add(_to_hashable_key(template))
699
761
 
700
762
  # Boundary and near-boundary sizes
701
763
  min_items = schema.get("minItems")
@@ -706,33 +768,48 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
706
768
  # One item more than minimum if possible
707
769
  larger = min_items + 1
708
770
  if larger not in seen and (max_items is None or larger <= max_items):
709
- yield PositiveValue(
710
- ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger}),
711
- description="Near-boundary items array",
712
- )
713
- seen.add(larger)
771
+ value = ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger})
772
+ key = _to_hashable_key(value)
773
+ if key not in seen:
774
+ seen.add(key)
775
+ yield PositiveValue(value, description="Near-boundary items array")
714
776
 
715
777
  if max_items is not None:
716
- if max_items < BUFFER_SIZE and max_items not in seen:
717
- yield PositiveValue(
718
- ctx.generate_from_schema({**schema, "minItems": max_items}),
719
- description="Maximum items array",
720
- )
721
- seen.add(max_items)
778
+ if max_items < INTERNAL_BUFFER_SIZE and max_items not in seen:
779
+ value = ctx.generate_from_schema({**schema, "minItems": max_items})
780
+ key = _to_hashable_key(value)
781
+ if key not in seen:
782
+ seen.add(key)
783
+ yield PositiveValue(value, description="Maximum items array")
722
784
 
723
785
  # One item smaller than maximum if possible
724
786
  smaller = max_items - 1
725
787
  if (
726
- smaller < BUFFER_SIZE
788
+ smaller < INTERNAL_BUFFER_SIZE
727
789
  and smaller > 0
728
790
  and smaller not in seen
729
791
  and (min_items is None or smaller >= min_items)
730
792
  ):
731
- yield PositiveValue(
732
- ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller}),
733
- description="Near-boundary items array",
734
- )
735
- seen.add(smaller)
793
+ value = ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller})
794
+ key = _to_hashable_key(value)
795
+ if key not in seen:
796
+ seen.add(key)
797
+ yield PositiveValue(value, description="Near-boundary items array")
798
+
799
+ if "items" in schema and "enum" in schema["items"] and isinstance(schema["items"]["enum"], list) and max_items != 0:
800
+ # Ensure there is enough items to pass `minItems` if it is specified
801
+ length = min_items or 1
802
+ for variant in schema["items"]["enum"]:
803
+ value = [variant] * length
804
+ key = _to_hashable_key(value)
805
+ if key not in seen:
806
+ seen.add(key)
807
+ yield PositiveValue(value, description="Enum value from available for items array")
808
+ elif min_items is None and max_items is None and "items" in schema and isinstance(schema["items"], dict):
809
+ # Otherwise only an empty array is generated
810
+ sub_schema = schema["items"]
811
+ for item in cover_schema_iter(ctx, sub_schema):
812
+ yield PositiveValue([item.value], description=f"Single-item array: {item.description}")
736
813
 
737
814
 
738
815
  def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator[GeneratedValue, None, None]:
@@ -5,11 +5,14 @@ DEFAULT_DEADLINE = 15000
5
5
 
6
6
 
7
7
  def setup() -> None:
8
+ from hypothesis import core as root_core
9
+ from hypothesis.internal.conjecture import engine
8
10
  from hypothesis.internal.entropy import deterministic_PRNG
9
11
  from hypothesis.internal.reflection import is_first_param_referenced_in_function
10
- from hypothesis.strategies._internal import core
12
+ from hypothesis.strategies._internal import collections, core
11
13
  from hypothesis_jsonschema import _from_schema, _resolve
12
14
 
15
+ from schemathesis.core import INTERNAL_BUFFER_SIZE
13
16
  from schemathesis.core.transforms import deepclone
14
17
 
15
18
  # Forcefully initializes Hypothesis' global PRNG to avoid races that initialize it
@@ -28,3 +31,6 @@ def setup() -> None:
28
31
  core.is_first_param_referenced_in_function = _is_first_param_referenced_in_function # type: ignore
29
32
  _resolve.deepcopy = deepclone # type: ignore
30
33
  _from_schema.deepcopy = deepclone # type: ignore
34
+ root_core.BUFFER_SIZE = INTERNAL_BUFFER_SIZE # type: ignore
35
+ engine.BUFFER_SIZE = INTERNAL_BUFFER_SIZE
36
+ collections.BUFFER_SIZE = INTERNAL_BUFFER_SIZE # type: ignore
@@ -1,17 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import os
4
5
  from dataclasses import dataclass, field
5
6
  from enum import Enum
6
7
  from functools import wraps
7
8
  from itertools import combinations
8
- import os
9
9
  from time import perf_counter
10
10
  from typing import Any, Callable, Generator, Mapping
11
11
 
12
12
  import hypothesis
13
13
  from hypothesis import Phase
14
14
  from hypothesis import strategies as st
15
+ from hypothesis._settings import all_settings
15
16
  from hypothesis.errors import Unsatisfiable
16
17
  from jsonschema.exceptions import SchemaError
17
18
 
@@ -98,7 +99,11 @@ def create_test(
98
99
  # Merge the user-provided settings with the current ones
99
100
  settings = hypothesis.settings(
100
101
  settings,
101
- **{item: value for item, value in config.settings.__dict__.items() if value != getattr(default, item)},
102
+ **{
103
+ item: getattr(config.settings, item)
104
+ for item in all_settings
105
+ if getattr(config.settings, item) != getattr(default, item)
106
+ },
102
107
  )
103
108
 
104
109
  if Phase.explain in settings.phases:
@@ -69,13 +69,18 @@ def _handle_parsed_pattern(parsed: list, pattern: str, min_length: int | None, m
69
69
  trailing_anchor_length = _get_anchor_length(parsed[2][1])
70
70
  leading_anchor = pattern[:leading_anchor_length]
71
71
  trailing_anchor = pattern[-trailing_anchor_length:]
72
- return (
73
- leading_anchor
74
- + _update_quantifier(
75
- op, value, pattern[leading_anchor_length:-trailing_anchor_length], min_length, max_length
76
- )
77
- + trailing_anchor
78
- )
72
+ # Special case for patterns canonicalisation. Some frameworks generate `\\w\\W` instead of `.`
73
+ # Such patterns lead to significantly slower data generation
74
+ if op == sre.IN and _matches_anything(value):
75
+ op = sre.ANY
76
+ value = None
77
+ inner_pattern = "."
78
+ elif op in REPEATS and len(value[2]) == 1 and value[2][0][0] == sre.IN and _matches_anything(value[2][0][1]):
79
+ value = (value[0], value[1], [(sre.ANY, None)], *value[3:])
80
+ inner_pattern = "."
81
+ else:
82
+ inner_pattern = pattern[leading_anchor_length:-trailing_anchor_length]
83
+ return leading_anchor + _update_quantifier(op, value, inner_pattern, min_length, max_length) + trailing_anchor
79
84
  elif (
80
85
  len(parsed) > 3
81
86
  and parsed[0][0] == ANCHOR
@@ -86,6 +91,19 @@ def _handle_parsed_pattern(parsed: list, pattern: str, min_length: int | None, m
86
91
  return pattern
87
92
 
88
93
 
94
+ def _matches_anything(value: list) -> bool:
95
+ """Check if the given pattern is equivalent to '.' (match any character)."""
96
+ # Common forms: [\w\W], [\s\S], etc.
97
+ return value in (
98
+ [(sre.CATEGORY, sre.CATEGORY_WORD), (sre.CATEGORY, sre.CATEGORY_NOT_WORD)],
99
+ [(sre.CATEGORY, sre.CATEGORY_SPACE), (sre.CATEGORY, sre.CATEGORY_NOT_SPACE)],
100
+ [(sre.CATEGORY, sre.CATEGORY_DIGIT), (sre.CATEGORY, sre.CATEGORY_NOT_DIGIT)],
101
+ [(sre.CATEGORY, sre.CATEGORY_NOT_WORD), (sre.CATEGORY, sre.CATEGORY_WORD)],
102
+ [(sre.CATEGORY, sre.CATEGORY_NOT_SPACE), (sre.CATEGORY, sre.CATEGORY_SPACE)],
103
+ [(sre.CATEGORY, sre.CATEGORY_NOT_DIGIT), (sre.CATEGORY, sre.CATEGORY_DIGIT)],
104
+ )
105
+
106
+
89
107
  def _handle_anchored_pattern(parsed: list, pattern: str, min_length: int | None, max_length: int | None) -> str:
90
108
  """Update regex pattern with multiple quantified patterns to satisfy length constraints."""
91
109
  # Extract anchors
@@ -269,15 +287,28 @@ def _get_anchor_length(node_type: int) -> int:
269
287
  return 1 # ^ or $ or their multiline/locale/unicode variants
270
288
 
271
289
 
272
- def _update_quantifier(op: int, value: tuple, pattern: str, min_length: int | None, max_length: int | None) -> str:
290
+ def _update_quantifier(
291
+ op: int, value: tuple | None, pattern: str, min_length: int | None, max_length: int | None
292
+ ) -> str:
273
293
  """Update the quantifier based on the operation type and given constraints."""
274
- if op in REPEATS:
294
+ if op in REPEATS and value is not None:
275
295
  return _handle_repeat_quantifier(value, pattern, min_length, max_length)
276
296
  if op in (LITERAL, IN) and max_length != 0:
277
297
  return _handle_literal_or_in_quantifier(pattern, min_length, max_length)
298
+ if op == sre.ANY and value is None:
299
+ # Equivalent to `.` which is in turn is the same as `.{1}`
300
+ return _handle_repeat_quantifier(
301
+ SINGLE_ANY,
302
+ pattern,
303
+ min_length,
304
+ max_length,
305
+ )
278
306
  return pattern
279
307
 
280
308
 
309
+ SINGLE_ANY = sre_parse.parse(".{1}")[0][1]
310
+
311
+
281
312
  def _handle_repeat_quantifier(
282
313
  value: tuple[int, int, tuple], pattern: str, min_length: int | None, max_length: int | None
283
314
  ) -> str:
@@ -1195,7 +1195,11 @@ class OpenApi30(SwaggerV20):
1195
1195
  files = []
1196
1196
  definition = operation.definition.raw
1197
1197
  if "$ref" in definition["requestBody"]:
1198
- body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
1198
+ self.resolver.push_scope(operation.definition.scope)
1199
+ try:
1200
+ body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
1201
+ finally:
1202
+ self.resolver.pop_scope()
1199
1203
  else:
1200
1204
  body = definition["requestBody"]
1201
1205
  content = body["content"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.0.0a8
3
+ Version: 4.0.0a10
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
@@ -25,7 +25,7 @@ schemathesis/cli/commands/run/validation.py,sha256=cpGG5hFc4lHVemXrQXRvrlNlqBmMq
25
25
  schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31MAXXn1qI7uU4FtiDwroXZI,1915
26
26
  schemathesis/cli/commands/run/handlers/base.py,sha256=yDsTtCiztLksfk7cRzg8JlaAVOfS-zwK3tsJMOXAFyc,530
27
27
  schemathesis/cli/commands/run/handlers/cassettes.py,sha256=SVk13xPhsQduCpgvvBwzEMDNTju-SHQCW90xTQ6iL1U,18525
28
- schemathesis/cli/commands/run/handlers/junitxml.py,sha256=c24UiwXqRCnv2eWQWEaNXLOghMI9JtGoZ9RTJY4ao6M,2350
28
+ schemathesis/cli/commands/run/handlers/junitxml.py,sha256=OhBS8JY4DXQGsQ0JjaHp19QDBR2hye30a-XulVzs-rg,2386
29
29
  schemathesis/cli/commands/run/handlers/output.py,sha256=n25QDGvYMXPKRPHBDckkDVwkkieY3Gq4HHSG0h3paTA,58800
30
30
  schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
31
  schemathesis/cli/ext/fs.py,sha256=OA3mRzra4rq3NyDTcBvlRh0WJrh4ByN-QQ8loI04m88,408
@@ -34,7 +34,7 @@ schemathesis/cli/ext/options.py,sha256=gBjfYPoiSoxCymWq41x0oKcQ2frv1fQnweETVpYiI
34
34
  schemathesis/contrib/__init__.py,sha256=wxpX86xrEGRAS3f7eugQfKVbnqV6ZfOqFBS_DmWxOok,120
35
35
  schemathesis/contrib/openapi/__init__.py,sha256=-7mBZ9RQj0EGzzmC-HKiT5ZslwHcoWFqCVpRG0GHO_o,162
36
36
  schemathesis/contrib/openapi/fill_missing_examples.py,sha256=BfBpuy3vCKbE_uILqPXnm7kxEDopAr5tNQwP5E9xX8A,585
37
- schemathesis/core/__init__.py,sha256=4SLVPpUZgZYABL6QPVRtanMemibKxTQEYcp4LFu4Jco,1761
37
+ schemathesis/core/__init__.py,sha256=s1Td4mecrTr31BjBSdnxPSWPPGQERkAAAEPZBBK_9AE,1794
38
38
  schemathesis/core/compat.py,sha256=Lflo6z-nQ6S4uKZINc4Fr90pd3LTN6cIG9HJJmmaHeY,754
39
39
  schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
40
40
  schemathesis/core/curl.py,sha256=yuaCe_zHLGwUjEeloQi6W3tOA3cGdnHDNI17-5jia0o,1723
@@ -74,13 +74,13 @@ schemathesis/engine/phases/unit/_pool.py,sha256=9OgmFd-ov1AAvcZGquK40PXkGLp7f2qC
74
74
  schemathesis/experimental/__init__.py,sha256=jYY3Mq6okqTRTMudPzcaT0JVjzJW5IN_ZVJdGU0stBs,2011
75
75
  schemathesis/generation/__init__.py,sha256=sWTRPTh-qDNkSfpM9rYI3v8zskH8_wFKUuPRg18fZI8,1627
76
76
  schemathesis/generation/case.py,sha256=Rt5MCUtPVYVQzNyjUx8magocPJpHV1svyuqQSTwUE-I,7306
77
- schemathesis/generation/coverage.py,sha256=vv2dbj_KAaqo8PCwMdyDWLyyAssk-YL5oTU3aCkgB1s,41185
77
+ schemathesis/generation/coverage.py,sha256=0iQZfm6yECy_nXatU3dCDCFI4lZW-IsiGZAiljptGUE,46362
78
78
  schemathesis/generation/meta.py,sha256=36h6m4E7jzLGa8TCvl7eBl_xUWLiRul3qxzexl5cB58,2515
79
79
  schemathesis/generation/modes.py,sha256=t_EvKr2aOXYMsEfdMu4lLF4KCGcX1LVVyvzTkcpJqhk,663
80
80
  schemathesis/generation/overrides.py,sha256=FhqcFoliEvgW6MZyFPYemfLgzKt3Miy8Cud7OMOCb7g,3045
81
81
  schemathesis/generation/targets.py,sha256=_rN2qgxTE2EfvygiN-Fy3WmDnRH0ERohdx3sKRDaYhU,2120
82
- schemathesis/generation/hypothesis/__init__.py,sha256=Rl7QwvMBMJI7pBqTydplX6bXC420n0EGQHVm-vZgaYQ,1204
83
- schemathesis/generation/hypothesis/builder.py,sha256=cSgFWQG-apIHNdW-PpIBDBjLw4RorihfZ4e_Ln3j2-w,30341
82
+ schemathesis/generation/hypothesis/__init__.py,sha256=SVwM-rx07jPZzms0idWYACgUtWAxh49HRuTnaQ__zf0,1549
83
+ schemathesis/generation/hypothesis/builder.py,sha256=QDfZRpFjQ0KYFPgu2BVSlxop0TQL7fQc201jOMR4rSQ,30472
84
84
  schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
85
85
  schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
86
86
  schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
@@ -121,9 +121,9 @@ schemathesis/specs/openapi/examples.py,sha256=Xvjp60QUcLaeGsJRbi2i6XM15_4uO0ceVo
121
121
  schemathesis/specs/openapi/formats.py,sha256=ViVF3aFeFI1ctwGQbiRDXhU3so82P0BCaF2aDDbUUm8,2816
122
122
  schemathesis/specs/openapi/media_types.py,sha256=ADedOaNWjbAtAekyaKmNj9fY6zBTeqcNqBEjN0EWNhI,1014
123
123
  schemathesis/specs/openapi/parameters.py,sha256=tVL61gDe9A8_jwoVKZZvpXKPerMyq7vkAvwdMsi44TI,14622
124
- schemathesis/specs/openapi/patterns.py,sha256=NLnGybcana_kYLVKVEjkEyAzdClAV0xKe4Oy4NVayMI,12834
124
+ schemathesis/specs/openapi/patterns.py,sha256=EQdf4net9QtwngKv36FEr7l0-3_afIMrrBdpKUWGWGc,14382
125
125
  schemathesis/specs/openapi/references.py,sha256=c8Ufa8hp6Dyf-gPn5lpmyqF_GtqXIBWoKkj3bk3WaPA,8871
126
- schemathesis/specs/openapi/schemas.py,sha256=zfGPFWnaI9_W8F8E8qCTzuYQRE5yDuGx7WGW4EH-QgI,55020
126
+ schemathesis/specs/openapi/schemas.py,sha256=dL1uLz_twgJZUdYBcs2JJ3b8ZlQH3nrGUg1p78pm9Os,55169
127
127
  schemathesis/specs/openapi/security.py,sha256=6UWYMhL-dPtkTineqqBFNKca1i4EuoTduw-EOLeE0aQ,7149
128
128
  schemathesis/specs/openapi/serialization.py,sha256=VdDLmeHqxlWM4cxQQcCkvrU6XurivolwEEaT13ohelA,11972
129
129
  schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
@@ -146,8 +146,8 @@ schemathesis/transport/prepare.py,sha256=qQ6zXBw5NN2AIM0bzLAc5Ryc3dmMb0R6xN14lnR
146
146
  schemathesis/transport/requests.py,sha256=j5wI1Uo_PnVuP1eV8l6ddsXosyxAPQ1mLSyWEZmTI9I,8747
147
147
  schemathesis/transport/serialization.py,sha256=jIMra1LqRGav0OX3Hx7mvORt38ll4cd2DKit2D58FN0,10531
148
148
  schemathesis/transport/wsgi.py,sha256=RWSuUXPrl91GxAy8a4jyNNozOWVMRBxKx_tljlWA_Lo,5697
149
- schemathesis-4.0.0a8.dist-info/METADATA,sha256=hdJd_ASqgZ75dZ1EW27p3mL6CAG179nTb-cm1zVtQqk,10427
150
- schemathesis-4.0.0a8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
151
- schemathesis-4.0.0a8.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
152
- schemathesis-4.0.0a8.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
153
- schemathesis-4.0.0a8.dist-info/RECORD,,
149
+ schemathesis-4.0.0a10.dist-info/METADATA,sha256=goxBn_HTzqqh7sSkqXpopuF5lT2tMxNJNcwyso9QtEQ,10428
150
+ schemathesis-4.0.0a10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
151
+ schemathesis-4.0.0a10.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
152
+ schemathesis-4.0.0a10.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
153
+ schemathesis-4.0.0a10.dist-info/RECORD,,