schemathesis 4.0.24__py3-none-any.whl → 4.0.26__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.
@@ -140,6 +140,13 @@ def _format_anyof_error(error: ValidationError) -> str:
140
140
  f"Error in {section} section:\n At least one filter is required when defining [[operations]].\n\n"
141
141
  "Please specify at least one include or exclude filter property (e.g., include-path, exclude-tag, etc.)."
142
142
  )
143
+ elif list(error.schema_path) == ["properties", "workers", "anyOf"]:
144
+ return (
145
+ f"Invalid value for 'workers': {repr(error.instance)}\n\n"
146
+ f"Expected either:\n"
147
+ f" - A positive integer (e.g., workers = 4)\n"
148
+ f' - The string "auto" for automatic detection (workers = "auto")'
149
+ )
143
150
  return error.message
144
151
 
145
152
 
@@ -94,7 +94,14 @@
94
94
  "type": "string"
95
95
  },
96
96
  "workers": {
97
- "type": "integer"
97
+ "anyOf": [
98
+ {
99
+ "type": "integer"
100
+ },
101
+ {
102
+ "const": "auto"
103
+ }
104
+ ]
98
105
  },
99
106
  "wait-for-schema": {
100
107
  "type": "number",
@@ -491,7 +498,14 @@
491
498
  "type": "string"
492
499
  },
493
500
  "workers": {
494
- "type": "integer"
501
+ "anyOf": [
502
+ {
503
+ "type": "integer"
504
+ },
505
+ {
506
+ "const": "auto"
507
+ }
508
+ ]
495
509
  },
496
510
  "wait-for-schema": {
497
511
  "type": "number",
@@ -263,12 +263,7 @@ def setup_hypothesis_database_key(test: Callable, operation: APIOperation) -> No
263
263
 
264
264
  It increases the effectiveness of the Hypothesis database in the CLI.
265
265
  """
266
- # Hypothesis's function digest depends on the test function signature. To reflect it for the web API case,
267
- # we use all API operation parameters in the digest.
268
- extra = operation.label.encode("utf8")
269
- for parameter in operation.iter_parameters():
270
- extra += parameter.serialize(operation).encode("utf8")
271
- test.hypothesis.inner_test._hypothesis_internal_add_digest = extra # type: ignore
266
+ test.hypothesis.inner_test._hypothesis_internal_add_digest = operation.label.encode("utf8") # type: ignore
272
267
 
273
268
 
274
269
  def get_invalid_regular_expression_message(warnings: list[WarningMessage]) -> str | None:
@@ -321,7 +321,7 @@ class HashSet:
321
321
 
322
322
 
323
323
  def _cover_positive_for_type(
324
- ctx: CoverageContext, schema: dict, ty: str | None
324
+ ctx: CoverageContext, schema: dict, ty: str | None, seen: HashSet | None = None
325
325
  ) -> Generator[GeneratedValue, None, None]:
326
326
  if ty == "object" or ty == "array":
327
327
  template_schema = _get_template_schema(schema, ty)
@@ -369,6 +369,9 @@ def _cover_positive_for_type(
369
369
  yield from _positive_object(ctx, schema, cast(dict, template))
370
370
  elif "properties" in schema or "required" in schema:
371
371
  yield from _positive_object(ctx, schema, cast(dict, template))
372
+ elif "not" in schema and isinstance(schema["not"], (dict, bool)):
373
+ nctx = ctx.with_negative()
374
+ yield from cover_schema_iter(nctx, schema["not"], seen)
372
375
 
373
376
 
374
377
  @contextmanager
@@ -397,9 +400,12 @@ def cover_schema_iter(
397
400
  ) -> Generator[GeneratedValue, None, None]:
398
401
  if seen is None:
399
402
  seen = HashSet()
400
- if isinstance(schema, bool):
403
+ if schema == {} or schema is True:
401
404
  types = ["null", "boolean", "string", "number", "array", "object"]
402
405
  schema = {}
406
+ elif schema is False:
407
+ types = []
408
+ schema = {"not": {}}
403
409
  else:
404
410
  types = schema.get("type", [])
405
411
  push_examples_to_properties(schema)
@@ -615,6 +621,9 @@ def cover_schema_iter(
615
621
  for value in cover_schema_iter(nctx, sub_schema, seen):
616
622
  if is_invalid_for_oneOf(value.value, idx, validators):
617
623
  yield value
624
+ elif key == "not" and isinstance(value, (dict, bool)):
625
+ pctx = ctx.with_positive()
626
+ yield from cover_schema_iter(pctx, value, seen)
618
627
 
619
628
 
620
629
  def is_valid_for_others(value: Any, idx: int, validators: list[jsonschema.Validator]) -> bool:
@@ -690,6 +699,7 @@ def _ensure_valid_headers_schema(schema: dict[str, Any]) -> dict[str, Any]:
690
699
  def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
691
700
  """Generate positive string values."""
692
701
  # Boundary and near boundary values
702
+ schema = {"type": "string", **schema}
693
703
  min_length = schema.get("minLength")
694
704
  if min_length == 0:
695
705
  min_length = None
@@ -791,6 +801,7 @@ def closest_multiple_greater_than(y: int, x: int) -> int:
791
801
  def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedValue, None, None]:
792
802
  """Generate positive integer values."""
793
803
  # Boundary and near boundary values
804
+ schema = {"type": "number", **schema}
794
805
  minimum = schema.get("minimum")
795
806
  maximum = schema.get("maximum")
796
807
  exclusive_minimum = schema.get("exclusiveMinimum")
@@ -7,6 +7,7 @@ def setup() -> None:
7
7
  from hypothesis.internal.entropy import deterministic_PRNG
8
8
  from hypothesis.internal.reflection import is_first_param_referenced_in_function
9
9
  from hypothesis.strategies._internal import collections, core
10
+ from hypothesis.vendor import pretty
10
11
  from hypothesis_jsonschema import _from_schema, _resolve
11
12
 
12
13
  from schemathesis.core import INTERNAL_BUFFER_SIZE
@@ -26,6 +27,14 @@ def setup() -> None:
26
27
  return is_first_param_referenced_in_function(f)
27
28
 
28
29
  core.is_first_param_referenced_in_function = _is_first_param_referenced_in_function # type: ignore
30
+
31
+ class RepresentationPrinter(pretty.RepresentationPrinter):
32
+ def pretty(self, obj: object) -> None:
33
+ # This one takes way too much - in the coverage phase it may give >2 orders of magnitude improvement
34
+ # depending on the schema size (~300 seconds -> 4.5 seconds in one of the benchmarks)
35
+ return None
36
+
37
+ root_core.RepresentationPrinter = RepresentationPrinter # type: ignore
29
38
  _resolve.deepcopy = deepclone # type: ignore
30
39
  _from_schema.deepcopy = deepclone # type: ignore
31
40
  root_core.BUFFER_SIZE = INTERNAL_BUFFER_SIZE # type: ignore
@@ -234,6 +234,22 @@ def add_examples(
234
234
  hook_dispatcher: HookDispatcher | None = None,
235
235
  **kwargs: Any,
236
236
  ) -> Callable:
237
+ for example in generate_example_cases(
238
+ test=test, operation=operation, fill_missing=fill_missing, hook_dispatcher=hook_dispatcher, **kwargs
239
+ ):
240
+ test = hypothesis.example(case=example)(test)
241
+
242
+ return test
243
+
244
+
245
+ def generate_example_cases(
246
+ *,
247
+ test: Callable,
248
+ operation: APIOperation,
249
+ fill_missing: bool,
250
+ hook_dispatcher: HookDispatcher | None = None,
251
+ **kwargs: Any,
252
+ ) -> Generator[Case]:
237
253
  """Add examples to the Hypothesis test, if they are specified in the schema."""
238
254
  from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
239
255
 
@@ -273,9 +289,7 @@ def add_examples(
273
289
  InvalidHeadersExampleMark.set(original_test, invalid_headers)
274
290
  continue
275
291
  adjust_urlencoded_payload(example)
276
- test = hypothesis.example(case=example)(test)
277
-
278
- return test
292
+ yield example
279
293
 
280
294
 
281
295
  def adjust_urlencoded_payload(case: Case) -> None:
@@ -298,6 +312,29 @@ def add_coverage(
298
312
  unexpected_methods: set[str],
299
313
  generation_config: GenerationConfig,
300
314
  ) -> Callable:
315
+ for case in generate_coverage_cases(
316
+ operation=operation,
317
+ generation_modes=generation_modes,
318
+ auth_storage=auth_storage,
319
+ as_strategy_kwargs=as_strategy_kwargs,
320
+ generate_duplicate_query_parameters=generate_duplicate_query_parameters,
321
+ unexpected_methods=unexpected_methods,
322
+ generation_config=generation_config,
323
+ ):
324
+ test = hypothesis.example(case=case)(test)
325
+ return test
326
+
327
+
328
+ def generate_coverage_cases(
329
+ *,
330
+ operation: APIOperation,
331
+ generation_modes: list[GenerationMode],
332
+ auth_storage: AuthStorage | None,
333
+ as_strategy_kwargs: dict[str, Any],
334
+ generate_duplicate_query_parameters: bool,
335
+ unexpected_methods: set[str],
336
+ generation_config: GenerationConfig,
337
+ ) -> Generator[Case]:
301
338
  from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
302
339
 
303
340
  auth_context = auths.AuthContext(
@@ -309,7 +346,6 @@ def add_coverage(
309
346
  for container in LOCATION_TO_CONTAINER.values()
310
347
  if container in as_strategy_kwargs
311
348
  }
312
-
313
349
  with warnings.catch_warnings():
314
350
  warnings.filterwarnings(
315
351
  "ignore", message=".*but this is not valid syntax for a Python regular expression.*", category=UserWarning
@@ -331,9 +367,7 @@ def add_coverage(
331
367
  setattr(case, container_name, value)
332
368
  else:
333
369
  container.update(value)
334
-
335
- test = hypothesis.example(case=case)(test)
336
- return test
370
+ yield case
337
371
 
338
372
 
339
373
  class Instant:
@@ -12,6 +12,7 @@ class TestPhase(str, Enum):
12
12
  EXAMPLES = "examples"
13
13
  COVERAGE = "coverage"
14
14
  FUZZING = "fuzzing"
15
+ STATEFUL = "stateful"
15
16
 
16
17
 
17
18
  class ComponentKind(str, Enum):
@@ -34,15 +35,22 @@ class ComponentInfo:
34
35
 
35
36
 
36
37
  @dataclass
37
- class GeneratePhaseData:
38
+ class FuzzingPhaseData:
38
39
  """Metadata specific to generate phase."""
39
40
 
40
41
  __slots__ = ()
41
42
 
42
43
 
43
44
  @dataclass
44
- class ExplicitPhaseData:
45
- """Metadata specific to explicit phase."""
45
+ class StatefulPhaseData:
46
+ """Metadata specific to stateful phase."""
47
+
48
+ __slots__ = ()
49
+
50
+
51
+ @dataclass
52
+ class ExamplesPhaseData:
53
+ """Metadata specific to examples phase."""
46
54
 
47
55
  __slots__ = ()
48
56
 
@@ -64,7 +72,7 @@ class PhaseInfo:
64
72
  """Phase-specific information."""
65
73
 
66
74
  name: TestPhase
67
- data: CoveragePhaseData | ExplicitPhaseData | GeneratePhaseData
75
+ data: CoveragePhaseData | ExamplesPhaseData | FuzzingPhaseData | StatefulPhaseData
68
76
 
69
77
  __slots__ = ("name", "data")
70
78
 
@@ -84,8 +92,8 @@ class PhaseInfo:
84
92
  )
85
93
 
86
94
  @classmethod
87
- def generate(cls) -> PhaseInfo:
88
- return cls(name=TestPhase.FUZZING, data=GeneratePhaseData())
95
+ def fuzzing(cls) -> PhaseInfo:
96
+ return cls(name=TestPhase.FUZZING, data=FuzzingPhaseData())
89
97
 
90
98
 
91
99
  @dataclass
schemathesis/schemas.py CHANGED
@@ -545,10 +545,6 @@ class Parameter:
545
545
  """Whether the parameter is required for a successful API call."""
546
546
  raise NotImplementedError
547
547
 
548
- def serialize(self, operation: APIOperation) -> str:
549
- """Get parameter's string representation."""
550
- raise NotImplementedError
551
-
552
548
 
553
549
  P = TypeVar("P", bound=Parameter)
554
550
 
@@ -620,7 +616,7 @@ class OperationDefinition(Generic[D]):
620
616
  def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
621
617
 
622
618
 
623
- @dataclass(eq=False)
619
+ @dataclass()
624
620
  class APIOperation(Generic[P]):
625
621
  """An API operation (e.g., `GET /users`)."""
626
622
 
@@ -647,6 +643,14 @@ class APIOperation(Generic[P]):
647
643
  def __deepcopy__(self, memo: dict) -> APIOperation[P]:
648
644
  return self
649
645
 
646
+ def __hash__(self) -> int:
647
+ return hash(self.label)
648
+
649
+ def __eq__(self, value: object, /) -> bool:
650
+ if not isinstance(value, APIOperation):
651
+ return NotImplemented
652
+ return self.label == value.label
653
+
650
654
  @property
651
655
  def full_path(self) -> str:
652
656
  return self.schema.get_full_path(self.path)
@@ -700,6 +704,9 @@ class APIOperation(Generic[P]):
700
704
  **kwargs: Extra arguments to the underlying strategy function.
701
705
 
702
706
  """
707
+ if self.schema.config.headers:
708
+ headers = kwargs.setdefault("headers", {})
709
+ headers.update(self.schema.config.headers)
703
710
  strategy = self.schema.get_case_strategy(self, generation_mode=generation_mode, **kwargs)
704
711
 
705
712
  def _apply_hooks(dispatcher: HookDispatcher, _strategy: SearchStrategy[Case]) -> SearchStrategy[Case]:
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import enum
4
4
  import time
5
- from dataclasses import dataclass, field
5
+ from dataclasses import dataclass
6
6
  from difflib import get_close_matches
7
7
  from enum import unique
8
8
  from types import SimpleNamespace
@@ -34,8 +34,8 @@ from schemathesis.generation.meta import (
34
34
  CaseMetadata,
35
35
  ComponentInfo,
36
36
  ComponentKind,
37
- ExplicitPhaseData,
38
- GeneratePhaseData,
37
+ ExamplesPhaseData,
38
+ FuzzingPhaseData,
39
39
  GenerationInfo,
40
40
  PhaseInfo,
41
41
  TestPhase,
@@ -50,7 +50,6 @@ from schemathesis.schemas import (
50
50
  )
51
51
  from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
52
52
 
53
- from ._cache import OperationCache
54
53
  from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
55
54
 
56
55
  if TYPE_CHECKING:
@@ -86,8 +85,6 @@ class GraphQLOperationDefinition(OperationDefinition):
86
85
 
87
86
  @dataclass
88
87
  class GraphQLSchema(BaseSchema):
89
- _operation_cache: OperationCache = field(default_factory=OperationCache)
90
-
91
88
  def __repr__(self) -> str:
92
89
  return f"<{self.__class__.__name__}>"
93
90
 
@@ -101,10 +98,6 @@ class GraphQLSchema(BaseSchema):
101
98
  yield operation_type.name
102
99
 
103
100
  def _get_operation_map(self, key: str) -> APIOperationMap:
104
- cache = self._operation_cache
105
- map = cache.get_map(key)
106
- if map is not None:
107
- return map
108
101
  schema = self.client_schema
109
102
  for root_type, operation_type in (
110
103
  (RootType.QUERY, schema.query_type),
@@ -113,7 +106,6 @@ class GraphQLSchema(BaseSchema):
113
106
  if operation_type and operation_type.name == key:
114
107
  map = APIOperationMap(self, {})
115
108
  map._data = FieldMap(map, root_type, operation_type)
116
- cache.insert_map(key, map)
117
109
  return map
118
110
  raise KeyError(key)
119
111
 
@@ -303,15 +295,9 @@ class FieldMap(Mapping):
303
295
 
304
296
  def _init_operation(self, field_name: str) -> APIOperation:
305
297
  schema = cast(GraphQLSchema, self._parent._schema)
306
- cache = schema._operation_cache
307
- operation = cache.get_operation(field_name)
308
- if operation is not None:
309
- return operation
310
298
  operation_type = self._operation_type
311
299
  field_ = operation_type.fields[field_name]
312
- operation = schema._build_operation(self._root_type, operation_type, field_name, field_)
313
- cache.insert_operation(field_name, operation)
314
- return operation
300
+ return schema._build_operation(self._root_type, operation_type, field_name, field_)
315
301
 
316
302
  def __getitem__(self, item: str) -> APIOperation:
317
303
  try:
@@ -368,10 +354,10 @@ def graphql_cases(
368
354
  query_ = _generate_parameter("query", query, draw, operation, hook_context, hooks)
369
355
 
370
356
  _phase_data = {
371
- TestPhase.EXAMPLES: ExplicitPhaseData(),
372
- TestPhase.FUZZING: GeneratePhaseData(),
357
+ TestPhase.EXAMPLES: ExamplesPhaseData(),
358
+ TestPhase.FUZZING: FuzzingPhaseData(),
373
359
  }[phase]
374
- phase_data = cast(Union[ExplicitPhaseData, GeneratePhaseData], _phase_data)
360
+ phase_data = cast(Union[ExamplesPhaseData, FuzzingPhaseData], _phase_data)
375
361
  instance = operation.Case(
376
362
  path_parameters=path_parameters_,
377
363
  headers=headers_,
@@ -5,7 +5,6 @@ from contextlib import suppress
5
5
  from dataclasses import dataclass
6
6
  from typing import Any, Callable, Dict, Iterable, Optional, Union, cast
7
7
  from urllib.parse import quote_plus
8
- from weakref import WeakKeyDictionary
9
8
 
10
9
  import jsonschema.protocols
11
10
  from hypothesis import event, note, reject
@@ -23,10 +22,11 @@ from schemathesis.generation.meta import (
23
22
  CaseMetadata,
24
23
  ComponentInfo,
25
24
  ComponentKind,
26
- ExplicitPhaseData,
27
- GeneratePhaseData,
25
+ ExamplesPhaseData,
26
+ FuzzingPhaseData,
28
27
  GenerationInfo,
29
28
  PhaseInfo,
29
+ StatefulPhaseData,
30
30
  TestPhase,
31
31
  )
32
32
  from schemathesis.openapi.generation.filters import is_valid_header, is_valid_path, is_valid_query, is_valid_urlencoded
@@ -64,7 +64,6 @@ def openapi_cases(
64
64
  body: Any = NOT_SET,
65
65
  media_type: str | None = None,
66
66
  phase: TestPhase = TestPhase.FUZZING,
67
- __is_stateful_phase: bool = False,
68
67
  ) -> Any:
69
68
  """A strategy that creates `Case` instances.
70
69
 
@@ -81,8 +80,7 @@ def openapi_cases(
81
80
  start = time.monotonic()
82
81
  strategy_factory = GENERATOR_MODE_TO_STRATEGY_FACTORY[generation_mode]
83
82
 
84
- phase_name = "stateful" if __is_stateful_phase else phase.value
85
- generation_config = operation.schema.config.generation_for(operation=operation, phase=phase_name)
83
+ generation_config = operation.schema.config.generation_for(operation=operation, phase=phase.value)
86
84
 
87
85
  ctx = HookContext(operation=operation)
88
86
 
@@ -151,10 +149,11 @@ def openapi_cases(
151
149
  reject()
152
150
 
153
151
  _phase_data = {
154
- TestPhase.EXAMPLES: ExplicitPhaseData(),
155
- TestPhase.FUZZING: GeneratePhaseData(),
152
+ TestPhase.EXAMPLES: ExamplesPhaseData(),
153
+ TestPhase.FUZZING: FuzzingPhaseData(),
154
+ TestPhase.STATEFUL: StatefulPhaseData(),
156
155
  }[phase]
157
- phase_data = cast(Union[ExplicitPhaseData, GeneratePhaseData], _phase_data)
156
+ phase_data = cast(Union[ExamplesPhaseData, FuzzingPhaseData, StatefulPhaseData], _phase_data)
158
157
 
159
158
  instance = operation.Case(
160
159
  media_type=media_type,
@@ -190,9 +189,6 @@ def openapi_cases(
190
189
  return instance
191
190
 
192
191
 
193
- _BODY_STRATEGIES_CACHE: WeakKeyDictionary = WeakKeyDictionary()
194
-
195
-
196
192
  def _get_body_strategy(
197
193
  parameter: OpenAPIBody,
198
194
  strategy_factory: StrategyFactory,
@@ -203,10 +199,6 @@ def _get_body_strategy(
203
199
 
204
200
  if parameter.media_type in MEDIA_TYPES:
205
201
  return MEDIA_TYPES[parameter.media_type]
206
- # The cache key relies on object ids, which means that the parameter should not be mutated
207
- # Note, the parent schema is not included as each parameter belong only to one schema
208
- if parameter in _BODY_STRATEGIES_CACHE and strategy_factory in _BODY_STRATEGIES_CACHE[parameter]:
209
- return _BODY_STRATEGIES_CACHE[parameter][strategy_factory]
210
202
  schema = parameter.as_json_schema(operation)
211
203
  schema = operation.schema.prepare_schema(schema)
212
204
  assert isinstance(operation.schema, BaseOpenAPISchema)
@@ -215,7 +207,6 @@ def _get_body_strategy(
215
207
  )
216
208
  if not parameter.is_required:
217
209
  strategy |= st.just(NOT_SET)
218
- _BODY_STRATEGIES_CACHE.setdefault(parameter, {})[strategy_factory] = strategy
219
210
  return strategy
220
211
 
221
212
 
@@ -248,9 +239,6 @@ def get_parameters_value(
248
239
  return value
249
240
 
250
241
 
251
- _PARAMETER_STRATEGIES_CACHE: WeakKeyDictionary = WeakKeyDictionary()
252
-
253
-
254
242
  @dataclass
255
243
  class ValueContainer:
256
244
  """Container for a value generated by a data generator or explicitly provided."""
@@ -352,10 +340,6 @@ def get_parameters_strategy(
352
340
 
353
341
  parameters = getattr(operation, LOCATION_TO_CONTAINER[location])
354
342
  if parameters:
355
- # The cache key relies on object ids, which means that the parameter should not be mutated
356
- nested_cache_key = (strategy_factory, location, tuple(sorted(exclude)))
357
- if operation in _PARAMETER_STRATEGIES_CACHE and nested_cache_key in _PARAMETER_STRATEGIES_CACHE[operation]:
358
- return _PARAMETER_STRATEGIES_CACHE[operation][nested_cache_key]
359
343
  schema = get_schema_for_location(operation, location, parameters)
360
344
  if location == "header" and exclude:
361
345
  # Remove excluded headers case-insensitively
@@ -399,7 +383,6 @@ def get_parameters_strategy(
399
383
  strategy = strategy.map(quote_all).map(jsonify_python_specific_types)
400
384
  elif location == "query":
401
385
  strategy = strategy.map(jsonify_python_specific_types)
402
- _PARAMETER_STRATEGIES_CACHE.setdefault(operation, {})[nested_cache_key] = strategy
403
386
  return strategy
404
387
  # No parameters defined for this location
405
388
  return st.none()
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import json
4
3
  from dataclasses import dataclass
5
4
  from typing import TYPE_CHECKING, Any, ClassVar, Iterable
6
5
 
@@ -95,11 +94,6 @@ class OpenAPIParameter(Parameter):
95
94
  if key in self.supported_jsonschema_keywords or key.startswith("x-") or key == self.nullable_field
96
95
  }
97
96
 
98
- def serialize(self, operation: APIOperation) -> str:
99
- # For simplicity, JSON Schema semantics is not taken into account (e.g. 1 == 1.0)
100
- # I.e. two semantically equal schemas may have different representation
101
- return json.dumps(self.as_json_schema(operation), sort_keys=True)
102
-
103
97
 
104
98
  @dataclass(eq=False)
105
99
  class OpenAPI20Parameter(OpenAPIParameter):
@@ -170,6 +164,7 @@ class OpenAPI30Parameter(OpenAPIParameter):
170
164
  "minProperties",
171
165
  "required",
172
166
  "enum",
167
+ "const",
173
168
  "type",
174
169
  "allOf",
175
170
  "oneOf",
@@ -178,6 +173,14 @@ class OpenAPI30Parameter(OpenAPIParameter):
178
173
  "items",
179
174
  "properties",
180
175
  "additionalProperties",
176
+ "additionalItems",
177
+ "dependencies",
178
+ "if",
179
+ "then",
180
+ "else",
181
+ "patternProperties",
182
+ "propertyNames",
183
+ "contains",
181
184
  "format",
182
185
  "example",
183
186
  "examples",
@@ -47,7 +47,6 @@ from ...generation import GenerationMode
47
47
  from ...hooks import HookContext, HookDispatcher
48
48
  from ...schemas import APIOperation, APIOperationMap, ApiStatistic, BaseSchema, OperationDefinition
49
49
  from . import serialization
50
- from ._cache import OperationCache
51
50
  from ._hypothesis import openapi_cases
52
51
  from .converter import to_json_schema, to_json_schema_recursive
53
52
  from .definitions import OPENAPI_30_VALIDATOR, OPENAPI_31_VALIDATOR, SWAGGER_20_VALIDATOR
@@ -96,7 +95,6 @@ class BaseOpenAPISchema(BaseSchema):
96
95
  links_field: ClassVar[str] = ""
97
96
  header_required_field: ClassVar[str] = ""
98
97
  security: ClassVar[BaseSecurityProcessor] = None # type: ignore
99
- _operation_cache: OperationCache = field(default_factory=OperationCache)
100
98
  _inline_reference_cache: dict[str, Any] = field(default_factory=dict)
101
99
  # Inline references cache can be populated from multiple threads, therefore we need some synchronisation to avoid
102
100
  # excessive resolving
@@ -115,17 +113,12 @@ class BaseOpenAPISchema(BaseSchema):
115
113
  return iter(self.raw_schema.get("paths", {}))
116
114
 
117
115
  def _get_operation_map(self, path: str) -> APIOperationMap:
118
- cache = self._operation_cache
119
- map = cache.get_map(path)
120
- if map is not None:
121
- return map
122
116
  path_item = self.raw_schema.get("paths", {})[path]
123
117
  with in_scope(self.resolver, self.location or ""):
124
118
  scope, path_item = self._resolve_path_item(path_item)
125
119
  self.dispatch_hook("before_process_path", HookContext(), path, path_item)
126
120
  map = APIOperationMap(self, {})
127
121
  map._data = MethodMap(map, scope, path, CaseInsensitiveDict(path_item))
128
- cache.insert_map(path, map)
129
122
  return map
130
123
 
131
124
  def find_operation_by_label(self, label: str) -> APIOperation | None:
@@ -136,7 +129,7 @@ class BaseOpenAPISchema(BaseSchema):
136
129
  matches = get_close_matches(item, list(self))
137
130
  self._on_missing_operation(item, exc, matches)
138
131
 
139
- def _on_missing_operation(self, item: str, exc: KeyError, matches: list[str]) -> NoReturn:
132
+ def _on_missing_operation(self, item: str, exc: KeyError | None, matches: list[str]) -> NoReturn:
140
133
  message = f"`{item}` not found"
141
134
  if matches:
142
135
  message += f". Did you mean `{matches[0]}`?"
@@ -443,31 +436,6 @@ class BaseOpenAPISchema(BaseSchema):
443
436
 
444
437
  def get_operation_by_id(self, operation_id: str) -> APIOperation:
445
438
  """Get an `APIOperation` instance by its `operationId`."""
446
- cache = self._operation_cache
447
- cached = cache.get_operation_by_id(operation_id)
448
- if cached is not None:
449
- return cached
450
- # Operation has not been accessed yet, need to populate the cache
451
- if not cache.has_ids_to_definitions:
452
- self._populate_operation_id_cache(cache)
453
- try:
454
- entry = cache.get_definition_by_id(operation_id)
455
- except KeyError as exc:
456
- matches = get_close_matches(operation_id, cache.known_operation_ids)
457
- self._on_missing_operation(operation_id, exc, matches)
458
- # It could've been already accessed in a different place
459
- traversal_key = (entry.scope, entry.path, entry.method)
460
- instance = cache.get_operation_by_traversal_key(traversal_key)
461
- if instance is not None:
462
- return instance
463
- resolved = self._resolve_operation(entry.operation)
464
- parameters = self._collect_operation_parameters(entry.path_item, resolved)
465
- initialized = self.make_operation(entry.path, entry.method, parameters, entry.operation, resolved, entry.scope)
466
- cache.insert_operation(initialized, traversal_key=traversal_key, operation_id=operation_id)
467
- return initialized
468
-
469
- def _populate_operation_id_cache(self, cache: OperationCache) -> None:
470
- """Collect all operation IDs from the schema."""
471
439
  resolve = self.resolver.resolve
472
440
  default_scope = self.resolver.resolution_scope
473
441
  for path, path_item in self.raw_schema.get("paths", {}).items():
@@ -477,44 +445,29 @@ class BaseOpenAPISchema(BaseSchema):
477
445
  scope, path_item = resolve(path_item["$ref"])
478
446
  else:
479
447
  scope = default_scope
480
- for key, entry in path_item.items():
481
- if key not in HTTP_METHODS:
448
+ for method, operation in path_item.items():
449
+ if method not in HTTP_METHODS:
482
450
  continue
483
- if "operationId" in entry:
484
- cache.insert_definition_by_id(
485
- entry["operationId"],
486
- path=path,
487
- method=key,
488
- scope=scope,
489
- path_item=path_item,
490
- operation=entry,
491
- )
451
+ if "operationId" in operation and operation["operationId"] == operation_id:
452
+ resolved = self._resolve_operation(operation)
453
+ parameters = self._collect_operation_parameters(path_item, resolved)
454
+ return self.make_operation(path, method, parameters, operation, resolved, scope)
455
+ self._on_missing_operation(operation_id, None, [])
492
456
 
493
457
  def get_operation_by_reference(self, reference: str) -> APIOperation:
494
458
  """Get local or external `APIOperation` instance by reference.
495
459
 
496
460
  Reference example: #/paths/~1users~1{user_id}/patch
497
461
  """
498
- cache = self._operation_cache
499
- cached = cache.get_operation_by_reference(reference)
500
- if cached is not None:
501
- return cached
502
462
  scope, operation = self.resolver.resolve(reference)
503
463
  path, method = scope.rsplit("/", maxsplit=2)[-2:]
504
464
  path = path.replace("~1", "/").replace("~0", "~")
505
- # Check the traversal cache as it could've been populated in other places
506
- traversal_key = (self.resolver.resolution_scope, path, method)
507
- cached = cache.get_operation_by_traversal_key(traversal_key)
508
- if cached is not None:
509
- return cached
510
465
  with in_scope(self.resolver, scope):
511
466
  resolved = self._resolve_operation(operation)
512
467
  parent_ref, _ = reference.rsplit("/", maxsplit=1)
513
468
  _, path_item = self.resolver.resolve(parent_ref)
514
469
  parameters = self._collect_operation_parameters(path_item, resolved)
515
- initialized = self.make_operation(path, method, parameters, operation, resolved, scope)
516
- cache.insert_operation(initialized, traversal_key=traversal_key, reference=reference)
517
- return initialized
470
+ return self.make_operation(path, method, parameters, operation, resolved, scope)
518
471
 
519
472
  def get_case_strategy(
520
473
  self,
@@ -851,22 +804,15 @@ class MethodMap(Mapping):
851
804
  method = method.lower()
852
805
  operation = self._path_item[method]
853
806
  schema = cast(BaseOpenAPISchema, self._parent._schema)
854
- cache = schema._operation_cache
855
807
  path = self._path
856
808
  scope = self._scope
857
- traversal_key = (scope, path, method)
858
- cached = cache.get_operation_by_traversal_key(traversal_key)
859
- if cached is not None:
860
- return cached
861
809
  schema.resolver.push_scope(scope)
862
810
  try:
863
811
  resolved = schema._resolve_operation(operation)
864
812
  finally:
865
813
  schema.resolver.pop_scope()
866
814
  parameters = schema._collect_operation_parameters(self._path_item, resolved)
867
- initialized = schema.make_operation(path, method, parameters, operation, resolved, scope)
868
- cache.insert_operation(initialized, traversal_key=traversal_key, operation_id=resolved.get("operationId"))
869
- return initialized
815
+ return schema.make_operation(path, method, parameters, operation, resolved, scope)
870
816
 
871
817
  def __getitem__(self, item: str) -> APIOperation:
872
818
  try:
@@ -15,7 +15,7 @@ from schemathesis.engine.recorder import ScenarioRecorder
15
15
  from schemathesis.generation import GenerationMode
16
16
  from schemathesis.generation.case import Case
17
17
  from schemathesis.generation.hypothesis import strategies
18
- from schemathesis.generation.meta import ComponentInfo, ComponentKind
18
+ from schemathesis.generation.meta import ComponentInfo, ComponentKind, TestPhase
19
19
  from schemathesis.generation.stateful import STATEFUL_TESTS_LABEL
20
20
  from schemathesis.generation.stateful.state_machine import APIStateMachine, StepInput, StepOutput, _normalize_name
21
21
  from schemathesis.schemas import APIOperation
@@ -179,10 +179,10 @@ def create_state_machine(schema: BaseOpenAPISchema) -> type[APIStateMachine]:
179
179
  name = _normalize_name(f"RANDOM -> {target.label}")
180
180
  config = schema.config.generation_for(operation=target, phase="stateful")
181
181
  if len(config.modes) == 1:
182
- case_strategy = target.as_strategy(generation_mode=config.modes[0], __is_stateful_phase=True)
182
+ case_strategy = target.as_strategy(generation_mode=config.modes[0], phase=TestPhase.STATEFUL)
183
183
  else:
184
184
  _strategies = {
185
- method: target.as_strategy(generation_mode=method, __is_stateful_phase=True)
185
+ method: target.as_strategy(generation_mode=method, phase=TestPhase.STATEFUL)
186
186
  for method in config.modes
187
187
  }
188
188
 
@@ -269,7 +269,7 @@ def into_step_input(
269
269
  ):
270
270
  kwargs["body"] = transition.request_body.value.ok()
271
271
  cases = strategies.combine(
272
- [target.as_strategy(generation_mode=mode, __is_stateful_phase=True, **kwargs) for mode in modes]
272
+ [target.as_strategy(generation_mode=mode, phase=TestPhase.STATEFUL, **kwargs) for mode in modes]
273
273
  )
274
274
  case = draw(cases)
275
275
  if (
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.0.24
3
+ Version: 4.0.26
4
4
  Summary: Property-based testing framework for Open API and GraphQL based apps
5
5
  Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
6
6
  Project-URL: Changelog, https://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
@@ -5,7 +5,7 @@ schemathesis/errors.py,sha256=T8nobEi5tQX_SkwaYb8JFoIlF9F_vOQVprZ8EVPAhjA,931
5
5
  schemathesis/filters.py,sha256=OEub50Ob5sf0Tn3iTeuIaxSMtepF7KVoiEM9wtt5GGA,13433
6
6
  schemathesis/hooks.py,sha256=OEnZw5lKSweNdycBRIOsn085rTB-L-iJAjj4ClldkU4,13932
7
7
  schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- schemathesis/schemas.py,sha256=glaIjN7JO1mdOCs5_HUFjLWQ-89z8NFBUIuc5p8cLAU,28386
8
+ schemathesis/schemas.py,sha256=thyBKI50sJsj7PE52GvLy9_ktPMDP-fbLM8ZwvdHuD0,28618
9
9
  schemathesis/cli/__init__.py,sha256=U9gjzWWpiFhaqevPjZbwyTNjABdpvXETI4HgwdGKnvs,877
10
10
  schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
11
11
  schemathesis/cli/constants.py,sha256=CVcQNHEiX-joAQmyuEVKWPOSxDHsOw_EXXZsEclzLuY,341
@@ -33,7 +33,7 @@ schemathesis/config/_auth.py,sha256=83RLVPm97W2thbn-yi01Rt94YwOxLG_a5VoxhEfjUjs,
33
33
  schemathesis/config/_checks.py,sha256=F0r16eSSiICvoiTUkNNOE2PH73EGd8bikoeZdME_3Yw,10763
34
34
  schemathesis/config/_diff_base.py,sha256=-XqS6cTzZC5fplz8_2RSZHDMSAPJhBBIEP6H8wcgHmo,4221
35
35
  schemathesis/config/_env.py,sha256=8XfIyrnGNQuCDnfG0lwmKRFbasRUjgeQGBAMupsmtOU,613
36
- schemathesis/config/_error.py,sha256=TxuuqQ1olwJc7P7ssfxXb1dB_Xn5uVsoazjvYvRxrxA,5437
36
+ schemathesis/config/_error.py,sha256=jfv9chQ4NGoDYypszNGymr0zxXVo65yP0AWK1WVEPIM,5781
37
37
  schemathesis/config/_generation.py,sha256=giWs4z17z9nRe_9Z3mAZ3LEoyh4hkcJnlAA6LSy6iEo,5210
38
38
  schemathesis/config/_health_check.py,sha256=zC9inla5ibMBlEy5WyM4_TME7ju_KH3Bwfo21RI3Gks,561
39
39
  schemathesis/config/_operations.py,sha256=2M36b4MMoFtaaFpe9yG-aWRqh0Qm1dpdk5M0V23X2yA,12129
@@ -45,7 +45,7 @@ schemathesis/config/_rate_limit.py,sha256=ekEW-jP_Ichk_O6hYpj-h2TTTKfp7Fm0nyFUbv
45
45
  schemathesis/config/_report.py,sha256=ZECDpaCY4WWHD5UbjvgZoSjLz-rlTvfd5Ivzdgzqf2I,3891
46
46
  schemathesis/config/_validator.py,sha256=IcE8geFZ0ZwR18rkIRs25i7pTl7Z84XbjYGUB-mqReU,258
47
47
  schemathesis/config/_warnings.py,sha256=sI0VZcTj3dOnphhBwYwU_KTagxr89HGWTtQ99HcY84k,772
48
- schemathesis/config/schema.json,sha256=wC1qe9M_fXotfmlBOmW_FCTRw9K5YC814-PipMGKllE,18907
48
+ schemathesis/config/schema.json,sha256=AzWZmZaAuAk4g7-EL_-LwGux3VScg81epWdPfz-kqG0,19127
49
49
  schemathesis/core/__init__.py,sha256=j862XBH5dXhxsrDg9mE7n4cSSfol0EHdY0ru1d27tCc,1917
50
50
  schemathesis/core/compat.py,sha256=9BWCrFoqN2sJIaiht_anxe8kLjYMR7t0iiOkXqLRUZ8,1058
51
51
  schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
@@ -81,17 +81,17 @@ schemathesis/engine/phases/stateful/__init__.py,sha256=Lz1rgNqCfUSIz173XqCGsiMuU
81
81
  schemathesis/engine/phases/stateful/_executor.py,sha256=_303Yqflx1iFNTQI2EfjSp_2T21YvzJJgMSazhpv5JQ,15200
82
82
  schemathesis/engine/phases/stateful/context.py,sha256=A7X1SLDOWFpCvFN9IiIeNVZM0emjqatmJL_k9UsO7vM,2946
83
83
  schemathesis/engine/phases/unit/__init__.py,sha256=BvZh39LZmXg90Cy_Tn0cQY5y7eWzYvAEmJ43fGKFAt8,8715
84
- schemathesis/engine/phases/unit/_executor.py,sha256=9MmZoKSBVSPk0LWwN3PZ3iaO9nzpT1Z70yzdEE48YYw,16489
84
+ schemathesis/engine/phases/unit/_executor.py,sha256=tRv_QjwtXOuSZCl_j-S_EnhcrKScr9gyGGZItzg16XI,16190
85
85
  schemathesis/engine/phases/unit/_pool.py,sha256=iU0hdHDmohPnEv7_S1emcabuzbTf-Cznqwn0pGQ5wNQ,2480
86
86
  schemathesis/generation/__init__.py,sha256=tvNO2FLiY8z3fZ_kL_QJhSgzXfnT4UqwSXMHCwfLI0g,645
87
87
  schemathesis/generation/case.py,sha256=zwAwFQ-Fp7SOxCXYOQyAdwAtNwVJe63PdLpvqackFQY,12296
88
- schemathesis/generation/coverage.py,sha256=2GZIyVfaZ8wAcF1GQohm9pftwNIVeeu5uWhYp8qHKlA,55606
89
- schemathesis/generation/meta.py,sha256=adkoMuCfzSjHJ9ZDocQn0GnVldSCkLL3eVR5A_jafwM,2552
88
+ schemathesis/generation/coverage.py,sha256=UUtY9OfL8TnSslWCrh17njN6Ik99u_qKxfUqcNNdHKw,56166
89
+ schemathesis/generation/meta.py,sha256=yYR7EB1f5n7RrzWHZ6YATepurnnc_hEe7HnztRbaaA0,2699
90
90
  schemathesis/generation/metrics.py,sha256=cZU5HdeAMcLFEDnTbNE56NuNq4P0N4ew-g1NEz5-kt4,2836
91
91
  schemathesis/generation/modes.py,sha256=Q1fhjWr3zxabU5qdtLvKfpMFZJAwlW9pnxgenjeXTyU,481
92
92
  schemathesis/generation/overrides.py,sha256=OBWqDQPreiliaf2M-oyXppVKHoJkCRzxtwSJx1b6AFw,3759
93
- schemathesis/generation/hypothesis/__init__.py,sha256=76QYchbfeA-A_G_bP6_BkG5JO9SxIIP1yNRBZa06RvE,1495
94
- schemathesis/generation/hypothesis/builder.py,sha256=f_5VsqeHKP-07pJZ0CPpW6YSxs9qUEseanAaxwOwdm4,34970
93
+ schemathesis/generation/hypothesis/__init__.py,sha256=jK3G4i0SdcyhqwPQg91RH_yg437lSY-smeIQ-wZLWPc,1959
94
+ schemathesis/generation/hypothesis/builder.py,sha256=zOUIp1n6kKkh_M7HiwtCNzDk3wtepwBEjmT2qt6PenM,36078
95
95
  schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
96
96
  schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
97
97
  schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
@@ -116,14 +116,12 @@ schemathesis/python/asgi.py,sha256=5PyvuTBaivvyPUEi3pwJni91K1kX5Zc0u9c6c1D8a1Q,2
116
116
  schemathesis/python/wsgi.py,sha256=uShAgo_NChbfYaV1117e6UHp0MTg7jaR0Sy_to3Jmf8,219
117
117
  schemathesis/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
118
118
  schemathesis/specs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
- schemathesis/specs/graphql/_cache.py,sha256=mlOtzEvdE9gZ1posP7OqHUYlaZyiygL5U_Xda7wphFc,988
120
119
  schemathesis/specs/graphql/nodes.py,sha256=bE3G1kNmqJ8OV4igBvIK-UORrkQA6Nofduf87O3TD9I,541
121
120
  schemathesis/specs/graphql/scalars.py,sha256=6lew8mnwhrtg23leiEbG43mLGPLlRln8mClCY94XpDA,2680
122
- schemathesis/specs/graphql/schemas.py,sha256=ezkqgMwx37tMWlhy_I0ahDF1Q44emDSJkyjduq1QGjE,14624
121
+ schemathesis/specs/graphql/schemas.py,sha256=B6FULWIIWoN_gx9OeQOd-6qWsz9yVg5h1XlxFe06XPM,14097
123
122
  schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
124
123
  schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
125
- schemathesis/specs/openapi/_cache.py,sha256=HpglmETmZU0RCHxp3DO_sg5_B_nzi54Zuw9vGzzYCxY,4295
126
- schemathesis/specs/openapi/_hypothesis.py,sha256=shKwQPWupBSZgZ1m35HjSJi0HX0Yv5wqjdkYWA_EE7U,22399
124
+ schemathesis/specs/openapi/_hypothesis.py,sha256=-DE9jxLcEYn5S5wYTQmIC8Lr8UgyXZ8zIhPaB7qRakc,21308
127
125
  schemathesis/specs/openapi/checks.py,sha256=1_YIcGqZ2xSN_1Ob_CIt_HfARSodMCCYNX4SQXx03-c,30150
128
126
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
129
127
  schemathesis/specs/openapi/converter.py,sha256=LkpCCAxZzET4Qa_3YStSNuhGlsm5G6TVwpxYu6lPO4g,4169
@@ -131,10 +129,10 @@ schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9
131
129
  schemathesis/specs/openapi/examples.py,sha256=JsAy8Dexw36SUAO5MBB44ji3zEK9Y_JU5d2LRshjEI4,22023
132
130
  schemathesis/specs/openapi/formats.py,sha256=8AIS7Uey-Z1wm1WYRqnsVqHwG9d316PdqfKLqwUs7us,3516
133
131
  schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKRcUOc41eEwKbo,2472
134
- schemathesis/specs/openapi/parameters.py,sha256=ifu_QQCMUzsUHQAkvsOvLuokns6CzesssmQ3Nd3zxII,14594
132
+ schemathesis/specs/openapi/parameters.py,sha256=XpuZ2sex2aYUzKDK17GXVNWFBmvamuyVtloQ1oGQhLk,14468
135
133
  schemathesis/specs/openapi/patterns.py,sha256=GqPZEXMRdWENQxanWjBOalIZ2MQUjuxk21kmdiI703E,18027
136
134
  schemathesis/specs/openapi/references.py,sha256=40YcDExPLR2B8EOwt-Csw-5MYFi2xj_DXf91J0Pc9d4,8855
137
- schemathesis/specs/openapi/schemas.py,sha256=ZhFYxYzm0u9VpPMD5f_tt15l29OMa4-lrofOWf4Q6yM,52790
135
+ schemathesis/specs/openapi/schemas.py,sha256=71fdSVpC3_EDjIMMuQdZV7T32IzhdEHPEVTCZkyE3Co,50301
138
136
  schemathesis/specs/openapi/security.py,sha256=6UWYMhL-dPtkTineqqBFNKca1i4EuoTduw-EOLeE0aQ,7149
139
137
  schemathesis/specs/openapi/serialization.py,sha256=VdDLmeHqxlWM4cxQQcCkvrU6XurivolwEEaT13ohelA,11972
140
138
  schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
@@ -148,7 +146,7 @@ schemathesis/specs/openapi/negative/__init__.py,sha256=1sajF22SrE4pUK7-C6PiseZ9P
148
146
  schemathesis/specs/openapi/negative/mutations.py,sha256=xDSUVnGWjuuIcvmW_mJGChf-G-nXst-JBX1okQAzon4,19865
149
147
  schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
150
148
  schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
151
- schemathesis/specs/openapi/stateful/__init__.py,sha256=t5O84nKXIk_NhFihuSip3EWMLBo4_AG95W_YVbTKPl4,15985
149
+ schemathesis/specs/openapi/stateful/__init__.py,sha256=DaV7Uuo1GTgZF1JjjoQzbzZw5HQhL6pTByiWqIlWwy4,15996
152
150
  schemathesis/specs/openapi/stateful/control.py,sha256=QaXLSbwQWtai5lxvvVtQV3BLJ8n5ePqSKB00XFxp-MA,3695
153
151
  schemathesis/specs/openapi/stateful/links.py,sha256=h5q40jUbcIk5DS_Tih1cvFJxS_QxxG0_9ZQnTs1A_zo,8806
154
152
  schemathesis/transport/__init__.py,sha256=6yg_RfV_9L0cpA6qpbH-SL9_3ggtHQji9CZrpIkbA6s,5321
@@ -157,8 +155,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
157
155
  schemathesis/transport/requests.py,sha256=46aplzhSmBupegPNMawma-iJWCegWkEd6mzdWLTpgM4,10742
158
156
  schemathesis/transport/serialization.py,sha256=igUXKZ_VJ9gV7P0TUc5PDQBJXl_s0kK9T3ljGWWvo6E,10339
159
157
  schemathesis/transport/wsgi.py,sha256=KoAfvu6RJtzyj24VGB8e-Iaa9smpgXJ3VsM8EgAz2tc,6152
160
- schemathesis-4.0.24.dist-info/METADATA,sha256=QwSbcsUU-AKUCztu8bJ672YiCaXN7VMvx8sVkbKsuIo,8472
161
- schemathesis-4.0.24.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
- schemathesis-4.0.24.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
- schemathesis-4.0.24.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
- schemathesis-4.0.24.dist-info/RECORD,,
158
+ schemathesis-4.0.26.dist-info/METADATA,sha256=-G-Cvrj712t6wjZhCS-FOzJ_57yoPpQMM2scYjSAnI0,8472
159
+ schemathesis-4.0.26.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
160
+ schemathesis-4.0.26.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
161
+ schemathesis-4.0.26.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
162
+ schemathesis-4.0.26.dist-info/RECORD,,
@@ -1,35 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
-
6
- if TYPE_CHECKING:
7
- from ...schemas import APIOperation, APIOperationMap
8
-
9
-
10
- @dataclass
11
- class OperationCache:
12
- _maps: dict[str, APIOperationMap]
13
- _operations: dict[str, APIOperation]
14
-
15
- __slots__ = ("_maps", "_operations")
16
-
17
- def __init__(
18
- self,
19
- _maps: dict[str, APIOperationMap] | None = None,
20
- _operations: dict[str, APIOperation] | None = None,
21
- ) -> None:
22
- self._maps = _maps or {}
23
- self._operations = _operations or {}
24
-
25
- def get_map(self, key: str) -> APIOperationMap | None:
26
- return self._maps.get(key)
27
-
28
- def insert_map(self, key: str, value: APIOperationMap) -> None:
29
- self._maps[key] = value
30
-
31
- def get_operation(self, key: str) -> APIOperation | None:
32
- return self._operations.get(key)
33
-
34
- def insert_operation(self, key: str, value: APIOperation) -> None:
35
- self._operations[key] = value
@@ -1,122 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass, field
4
- from typing import TYPE_CHECKING, Any, Tuple
5
-
6
- if TYPE_CHECKING:
7
- from ...schemas import APIOperation, APIOperationMap
8
-
9
-
10
- @dataclass
11
- class OperationCacheEntry:
12
- path: str
13
- method: str
14
- # The resolution scope of the operation
15
- scope: str
16
- # Parent path item
17
- path_item: dict[str, Any]
18
- # Unresolved operation definition
19
- operation: dict[str, Any]
20
- __slots__ = ("path", "method", "scope", "path_item", "operation")
21
-
22
-
23
- # During traversal, we need to keep track of the scope, path, and method
24
- TraversalKey = Tuple[str, str, str]
25
- OperationId = str
26
- Reference = str
27
-
28
-
29
- @dataclass
30
- class OperationCache:
31
- """Cache for Open API operations.
32
-
33
- This cache contains multiple levels to avoid unnecessary parsing of the schema.
34
- """
35
-
36
- # Cache to avoid schema traversal on every access
37
- _id_to_definition: dict[OperationId, OperationCacheEntry] = field(default_factory=dict)
38
- # Map map between 1st & 2nd level cache keys
39
- # Even though 1st level keys could be directly mapped to Python objects in memory, we need to keep them separate
40
- # to ensure a single owner of the operation instance.
41
- _id_to_operation: dict[OperationId, int] = field(default_factory=dict)
42
- _traversal_key_to_operation: dict[TraversalKey, int] = field(default_factory=dict)
43
- _reference_to_operation: dict[Reference, int] = field(default_factory=dict)
44
- # The actual operations
45
- _operations: list[APIOperation] = field(default_factory=list)
46
- # Cache for operation maps
47
- _maps: dict[str, APIOperationMap] = field(default_factory=dict)
48
-
49
- @property
50
- def known_operation_ids(self) -> list[str]:
51
- return list(self._id_to_definition)
52
-
53
- @property
54
- def has_ids_to_definitions(self) -> bool:
55
- return bool(self._id_to_definition)
56
-
57
- def _append_operation(self, operation: APIOperation) -> int:
58
- idx = len(self._operations)
59
- self._operations.append(operation)
60
- return idx
61
-
62
- def insert_definition_by_id(
63
- self,
64
- operation_id: str,
65
- path: str,
66
- method: str,
67
- scope: str,
68
- path_item: dict[str, Any],
69
- operation: dict[str, Any],
70
- ) -> None:
71
- """Insert a new operation definition into cache."""
72
- self._id_to_definition[operation_id] = OperationCacheEntry(
73
- path=path, method=method, scope=scope, path_item=path_item, operation=operation
74
- )
75
-
76
- def get_definition_by_id(self, operation_id: str) -> OperationCacheEntry:
77
- """Get an operation definition by its ID."""
78
- # TODO: Avoid KeyError in the future
79
- return self._id_to_definition[operation_id]
80
-
81
- def insert_operation(
82
- self,
83
- operation: APIOperation,
84
- *,
85
- traversal_key: TraversalKey,
86
- operation_id: str | None = None,
87
- reference: str | None = None,
88
- ) -> None:
89
- """Insert a new operation into cache by one or multiple keys."""
90
- idx = self._append_operation(operation)
91
- self._traversal_key_to_operation[traversal_key] = idx
92
- if operation_id is not None:
93
- self._id_to_operation[operation_id] = idx
94
- if reference is not None:
95
- self._reference_to_operation[reference] = idx
96
-
97
- def get_operation_by_id(self, operation_id: str) -> APIOperation | None:
98
- """Get an operation by its ID."""
99
- idx = self._id_to_operation.get(operation_id)
100
- if idx is not None:
101
- return self._operations[idx]
102
- return None
103
-
104
- def get_operation_by_reference(self, reference: str) -> APIOperation | None:
105
- """Get an operation by its reference."""
106
- idx = self._reference_to_operation.get(reference)
107
- if idx is not None:
108
- return self._operations[idx]
109
- return None
110
-
111
- def get_operation_by_traversal_key(self, key: TraversalKey) -> APIOperation | None:
112
- """Get an operation by its traverse key."""
113
- idx = self._traversal_key_to_operation.get(key)
114
- if idx is not None:
115
- return self._operations[idx]
116
- return None
117
-
118
- def get_map(self, key: str) -> APIOperationMap | None:
119
- return self._maps.get(key)
120
-
121
- def insert_map(self, key: str, value: APIOperationMap) -> None:
122
- self._maps[key] = value