schemathesis 4.3.15__py3-none-any.whl → 4.3.17__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of schemathesis might be problematic. Click here for more details.

@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import inspect
4
5
  import warnings
5
6
  from dataclasses import dataclass
6
7
  from enum import Enum
@@ -42,6 +43,7 @@ from schemathesis.generation.meta import (
42
43
  CaseMetadata,
43
44
  ComponentInfo,
44
45
  CoveragePhaseData,
46
+ CoverageScenario,
45
47
  GenerationInfo,
46
48
  PhaseInfo,
47
49
  )
@@ -227,7 +229,7 @@ def create_base_test(
227
229
 
228
230
  funcobj = hypothesis.given(*args, **{**kwargs, "case": strategy})(test_wrapper)
229
231
 
230
- if asyncio.iscoroutinefunction(test_function):
232
+ if inspect.iscoroutinefunction(test_function):
231
233
  funcobj.hypothesis.inner_test = make_async_test(test_function) # type: ignore
232
234
  return funcobj
233
235
 
@@ -582,6 +584,7 @@ def _iter_coverage_cases(
582
584
  coverage.GeneratedValue(
583
585
  "value",
584
586
  generation_mode=GenerationMode.NEGATIVE,
587
+ scenario=CoverageScenario.UNSUPPORTED_PATH_PATTERN,
585
588
  description="Sample value for unsupported path parameter pattern",
586
589
  parameter=name,
587
590
  location="/",
@@ -638,6 +641,7 @@ def _iter_coverage_cases(
638
641
  ),
639
642
  components=data.components,
640
643
  phase=PhaseInfo.coverage(
644
+ scenario=value.scenario,
641
645
  description=value.description,
642
646
  location=value.location,
643
647
  parameter=body.media_type,
@@ -660,6 +664,7 @@ def _iter_coverage_cases(
660
664
  ),
661
665
  components=data.components,
662
666
  phase=PhaseInfo.coverage(
667
+ scenario=next_value.scenario,
663
668
  description=next_value.description,
664
669
  location=next_value.location,
665
670
  parameter=body.media_type,
@@ -680,7 +685,9 @@ def _iter_coverage_cases(
680
685
  mode=GenerationMode.POSITIVE,
681
686
  ),
682
687
  components=data.components,
683
- phase=PhaseInfo.coverage(description="Default positive test case"),
688
+ phase=PhaseInfo.coverage(
689
+ scenario=CoverageScenario.DEFAULT_POSITIVE_TEST, description="Default positive test case"
690
+ ),
684
691
  ),
685
692
  )
686
693
 
@@ -706,6 +713,7 @@ def _iter_coverage_cases(
706
713
  generation=GenerationInfo(time=instant.elapsed, mode=value.generation_mode),
707
714
  components=data.components,
708
715
  phase=PhaseInfo.coverage(
716
+ scenario=value.scenario,
709
717
  description=value.description,
710
718
  location=value.location,
711
719
  parameter=name,
@@ -725,7 +733,10 @@ def _iter_coverage_cases(
725
733
  _meta=CaseMetadata(
726
734
  generation=GenerationInfo(time=instant.elapsed, mode=GenerationMode.NEGATIVE),
727
735
  components=data.components,
728
- phase=PhaseInfo.coverage(description=f"Unspecified HTTP method: {method.upper()}"),
736
+ phase=PhaseInfo.coverage(
737
+ scenario=CoverageScenario.UNSPECIFIED_HTTP_METHOD,
738
+ description=f"Unspecified HTTP method: {method.upper()}",
739
+ ),
729
740
  ),
730
741
  )
731
742
  # Generate duplicate query parameters
@@ -750,6 +761,7 @@ def _iter_coverage_cases(
750
761
  generation=GenerationInfo(time=instant.elapsed, mode=GenerationMode.NEGATIVE),
751
762
  components=data.components,
752
763
  phase=PhaseInfo.coverage(
764
+ scenario=CoverageScenario.DUPLICATE_PARAMETER,
753
765
  description=f"Duplicate `{parameter.name}` query parameter",
754
766
  parameter=parameter.name,
755
767
  parameter_location=ParameterLocation.QUERY,
@@ -776,6 +788,7 @@ def _iter_coverage_cases(
776
788
  generation=GenerationInfo(time=instant.elapsed, mode=GenerationMode.NEGATIVE),
777
789
  components=data.components,
778
790
  phase=PhaseInfo.coverage(
791
+ scenario=CoverageScenario.MISSING_PARAMETER,
779
792
  description=f"Missing `{name}` at {location.value}",
780
793
  parameter=name,
781
794
  parameter_location=location,
@@ -802,6 +815,7 @@ def _iter_coverage_cases(
802
815
  # Helper function to create and yield a case
803
816
  def make_case(
804
817
  container_values: dict,
818
+ scenario: CoverageScenario,
805
819
  description: str,
806
820
  _location: ParameterLocation,
807
821
  _parameter: str | None,
@@ -818,6 +832,7 @@ def _iter_coverage_cases(
818
832
  ),
819
833
  components=data.components,
820
834
  phase=PhaseInfo.coverage(
835
+ scenario=scenario,
821
836
  description=description,
822
837
  parameter=_parameter,
823
838
  parameter_location=_location,
@@ -861,6 +876,7 @@ def _iter_coverage_cases(
861
876
  more = next(iterator)
862
877
  yield make_case(
863
878
  more.value,
879
+ more.scenario,
864
880
  more.description,
865
881
  _location,
866
882
  more.parameter,
@@ -876,6 +892,7 @@ def _iter_coverage_cases(
876
892
  if GenerationMode.POSITIVE in generation_modes:
877
893
  yield make_case(
878
894
  only_required,
895
+ CoverageScenario.OBJECT_ONLY_REQUIRED,
879
896
  "Only required properties",
880
897
  location,
881
898
  None,
@@ -891,8 +908,9 @@ def _iter_coverage_cases(
891
908
  assert case.meta is not None
892
909
  assert isinstance(case.meta.phase.data, CoveragePhaseData)
893
910
  # Already generated in one of the blocks above
894
- if location != "path" and not case.meta.phase.data.description.startswith(
895
- "Missing required property"
911
+ if (
912
+ location != "path"
913
+ and case.meta.phase.data.scenario != CoverageScenario.OBJECT_MISSING_REQUIRED_PROPERTY
896
914
  ):
897
915
  yield case
898
916
 
@@ -902,6 +920,7 @@ def _iter_coverage_cases(
902
920
  if combo != base_container and GenerationMode.POSITIVE in generation_modes:
903
921
  yield make_case(
904
922
  combo,
923
+ CoverageScenario.OBJECT_REQUIRED_AND_OPTIONAL,
905
924
  f"All required properties and optional '{opt_param}'",
906
925
  location,
907
926
  None,
@@ -914,8 +933,9 @@ def _iter_coverage_cases(
914
933
  assert case.meta is not None
915
934
  assert isinstance(case.meta.phase.data, CoveragePhaseData)
916
935
  # Already generated in one of the blocks above
917
- if location != "path" and not case.meta.phase.data.description.startswith(
918
- "Missing required property"
936
+ if (
937
+ location != "path"
938
+ and case.meta.phase.data.scenario != CoverageScenario.OBJECT_MISSING_REQUIRED_PROPERTY
919
939
  ):
920
940
  yield case
921
941
 
@@ -927,6 +947,7 @@ def _iter_coverage_cases(
927
947
  if combo != base_container:
928
948
  yield make_case(
929
949
  combo,
950
+ CoverageScenario.OBJECT_REQUIRED_AND_OPTIONAL,
930
951
  f"All required and {size} optional properties",
931
952
  location,
932
953
  None,
@@ -16,6 +16,75 @@ class TestPhase(str, Enum):
16
16
  STATEFUL = "stateful"
17
17
 
18
18
 
19
+ class CoverageScenario(str, Enum):
20
+ """Coverage test scenario types."""
21
+
22
+ # Positive scenarios - Valid values
23
+ EXAMPLE_VALUE = "example_value"
24
+ DEFAULT_VALUE = "default_value"
25
+ ENUM_VALUE = "enum_value"
26
+ CONST_VALUE = "const_value"
27
+ VALID_STRING = "valid_string"
28
+ VALID_NUMBER = "valid_number"
29
+ VALID_BOOLEAN = "valid_boolean"
30
+ VALID_ARRAY = "valid_array"
31
+ VALID_OBJECT = "valid_object"
32
+ NULL_VALUE = "null_value"
33
+
34
+ # Positive scenarios - Boundary values for strings
35
+ MINIMUM_LENGTH_STRING = "minimum_length_string"
36
+ MAXIMUM_LENGTH_STRING = "maximum_length_string"
37
+ NEAR_BOUNDARY_LENGTH_STRING = "near_boundary_length_string"
38
+
39
+ # Positive scenarios - Boundary values for numbers
40
+ MINIMUM_VALUE = "minimum_value"
41
+ MAXIMUM_VALUE = "maximum_value"
42
+ NEAR_BOUNDARY_NUMBER = "near_boundary_number"
43
+
44
+ # Positive scenarios - Boundary values for arrays
45
+ MINIMUM_ITEMS_ARRAY = "minimum_items_array"
46
+ MAXIMUM_ITEMS_ARRAY = "maximum_items_array"
47
+ NEAR_BOUNDARY_ITEMS_ARRAY = "near_boundary_items_array"
48
+ ENUM_VALUE_ITEMS_ARRAY = "enum_value_items_array"
49
+
50
+ # Positive scenarios - Objects
51
+ OBJECT_ONLY_REQUIRED = "object_only_required"
52
+ OBJECT_REQUIRED_AND_OPTIONAL = "object_required_and_optional"
53
+
54
+ # Positive scenarios - Default test case
55
+ DEFAULT_POSITIVE_TEST = "default_positive_test"
56
+
57
+ # Negative scenarios - Boundary violations for numbers
58
+ VALUE_ABOVE_MAXIMUM = "value_above_maximum"
59
+ VALUE_BELOW_MINIMUM = "value_below_minimum"
60
+
61
+ # Negative scenarios - Boundary violations for strings
62
+ STRING_ABOVE_MAX_LENGTH = "string_above_max_length"
63
+ STRING_BELOW_MIN_LENGTH = "string_below_min_length"
64
+
65
+ # Negative scenarios - Boundary violations for arrays
66
+ ARRAY_ABOVE_MAX_ITEMS = "array_above_max_items"
67
+ ARRAY_BELOW_MIN_ITEMS = "array_below_min_items"
68
+
69
+ # Negative scenarios - Constraint violations
70
+ OBJECT_UNEXPECTED_PROPERTIES = "object_unexpected_properties"
71
+ OBJECT_MISSING_REQUIRED_PROPERTY = "object_missing_required_property"
72
+ INCORRECT_TYPE = "incorrect_type"
73
+ INVALID_ENUM_VALUE = "invalid_enum_value"
74
+ INVALID_FORMAT = "invalid_format"
75
+ INVALID_PATTERN = "invalid_pattern"
76
+ NOT_MULTIPLE_OF = "not_multiple_of"
77
+ NON_UNIQUE_ITEMS = "non_unique_items"
78
+
79
+ # Negative scenarios - Missing parameters
80
+ MISSING_PARAMETER = "missing_parameter"
81
+ DUPLICATE_PARAMETER = "duplicate_parameter"
82
+
83
+ # Negative scenarios - Unsupported patterns
84
+ UNSUPPORTED_PATH_PATTERN = "unsupported_path_pattern"
85
+ UNSPECIFIED_HTTP_METHOD = "unspecified_http_method"
86
+
87
+
19
88
  @dataclass
20
89
  class ComponentInfo:
21
90
  """Information about how a specific component was generated."""
@@ -50,12 +119,13 @@ class ExamplesPhaseData:
50
119
  class CoveragePhaseData:
51
120
  """Metadata specific to coverage phase."""
52
121
 
122
+ scenario: CoverageScenario
53
123
  description: str
54
124
  location: str | None
55
125
  parameter: str | None
56
126
  parameter_location: ParameterLocation | None
57
127
 
58
- __slots__ = ("description", "location", "parameter", "parameter_location")
128
+ __slots__ = ("scenario", "description", "location", "parameter", "parameter_location")
59
129
 
60
130
 
61
131
  @dataclass
@@ -70,6 +140,7 @@ class PhaseInfo:
70
140
  @classmethod
71
141
  def coverage(
72
142
  cls,
143
+ scenario: CoverageScenario,
73
144
  description: str,
74
145
  location: str | None = None,
75
146
  parameter: str | None = None,
@@ -78,7 +149,11 @@ class PhaseInfo:
78
149
  return cls(
79
150
  name=TestPhase.COVERAGE,
80
151
  data=CoveragePhaseData(
81
- description=description, location=location, parameter=parameter, parameter_location=parameter_location
152
+ scenario=scenario,
153
+ description=description,
154
+ location=location,
155
+ parameter=parameter,
156
+ parameter_location=parameter_location,
82
157
  ),
83
158
  )
84
159
 
@@ -37,7 +37,6 @@ def get_all_tests(
37
37
  *,
38
38
  schema: BaseSchema,
39
39
  test_func: Callable,
40
- modes: list[HypothesisTestMode],
41
40
  settings: hypothesis.settings | None = None,
42
41
  seed: int | None = None,
43
42
  as_strategy_kwargs: Callable[[APIOperation], dict[str, Any]] | None = None,
@@ -51,11 +50,22 @@ def get_all_tests(
51
50
  _as_strategy_kwargs = as_strategy_kwargs(operation)
52
51
  else:
53
52
  _as_strategy_kwargs = {}
53
+
54
+ # Get modes from config for this operation
55
+ modes = []
56
+ phases = schema.config.phases_for(operation=operation)
57
+ if phases.examples.enabled:
58
+ modes.append(HypothesisTestMode.EXAMPLES)
59
+ if phases.fuzzing.enabled:
60
+ modes.append(HypothesisTestMode.FUZZING)
61
+ if phases.coverage.enabled:
62
+ modes.append(HypothesisTestMode.COVERAGE)
63
+
54
64
  test = create_test(
55
65
  operation=operation,
56
66
  test_func=test_func,
57
67
  config=HypothesisTestConfig(
58
- settings=settings,
68
+ settings=settings or schema.config.get_hypothesis_settings(operation=operation),
59
69
  modes=modes,
60
70
  seed=seed,
61
71
  project=schema.config,
@@ -170,31 +180,57 @@ class LazySchema:
170
180
  else:
171
181
  given_kwargs = {}
172
182
 
173
- def wrapped_test(request: FixtureRequest) -> None:
183
+ def wrapped_test(*args: Any, request: FixtureRequest, **kwargs: Any) -> None:
174
184
  """The actual test, which is executed by pytest."""
175
185
  __tracebackhide__ = True
186
+
187
+ # Load all checks eagerly, so they are accessible inside the test function
188
+ from schemathesis.checks import load_all_checks
189
+
190
+ load_all_checks()
191
+
176
192
  schema = get_schema(
177
193
  request=request,
178
194
  name=self.fixture_name,
179
195
  test_function=test_func,
180
196
  filter_set=self.filter_set,
181
197
  )
182
- fixtures = get_fixtures(test_func, request, given_kwargs)
198
+ # Check if test function is a method and inject self from request.instance
199
+ sig = signature(test_func)
200
+ if "self" in sig.parameters and request.instance is not None:
201
+ fixtures = {"self": request.instance}
202
+ fixtures.update(get_fixtures(test_func, request, given_kwargs))
203
+ else:
204
+ fixtures = get_fixtures(test_func, request, given_kwargs)
183
205
  # Changing the node id is required for better reporting - the method and path will appear there
184
206
  node_id = request.node._nodeid
185
207
  settings = getattr(wrapped_test, "_hypothesis_internal_use_settings", None)
186
208
 
187
209
  def as_strategy_kwargs(_operation: APIOperation) -> dict[str, Any]:
210
+ as_strategy_kwargs: dict[str, Any] = {}
211
+
212
+ auth = schema.config.auth_for(operation=_operation)
213
+ if auth is not None:
214
+ from requests.auth import _basic_auth_str
215
+
216
+ as_strategy_kwargs["headers"] = {"Authorization": _basic_auth_str(*auth)}
217
+
218
+ headers = schema.config.headers_for(operation=_operation)
219
+ if headers:
220
+ as_strategy_kwargs["headers"] = headers
221
+
188
222
  override = overrides.for_operation(config=schema.config, operation=_operation)
223
+ for location, entry in override.items():
224
+ if entry:
225
+ as_strategy_kwargs[location.container_name] = entry
189
226
 
190
- return {location.container_name: entry for location, entry in override.items() if entry}
227
+ return as_strategy_kwargs
191
228
 
192
229
  tests = list(
193
230
  get_all_tests(
194
231
  schema=schema,
195
232
  test_func=test_func,
196
233
  settings=settings,
197
- modes=list(HypothesisTestMode),
198
234
  as_strategy_kwargs=as_strategy_kwargs,
199
235
  given_kwargs=given_kwargs,
200
236
  )
@@ -213,14 +249,22 @@ class LazySchema:
213
249
  _schema_error(subtests, result.err(), node_id)
214
250
  subtests.item._nodeid = node_id
215
251
 
216
- wrapped_test = pytest.mark.usefixtures(self.fixture_name)(wrapped_test)
217
- _copy_marks(test_func, wrapped_test)
252
+ sig = signature(test_func)
253
+ if "self" in sig.parameters:
254
+ # For methods, wrap with staticmethod to prevent pytest from passing self
255
+ wrapped_test = staticmethod(wrapped_test) # type: ignore[assignment]
256
+ wrapped_func = wrapped_test.__func__ # type: ignore[attr-defined]
257
+ else:
258
+ wrapped_func = wrapped_test
259
+
260
+ wrapped_func = pytest.mark.usefixtures(self.fixture_name)(wrapped_func)
261
+ _copy_marks(test_func, wrapped_func)
218
262
 
219
263
  # Needed to prevent a failure when settings are applied to the test function
220
- wrapped_test.is_hypothesis_test = True # type: ignore
221
- wrapped_test.hypothesis = HypothesisHandle(test_func, wrapped_test, given_kwargs) # type: ignore
264
+ wrapped_func.is_hypothesis_test = True # type: ignore
265
+ wrapped_func.hypothesis = HypothesisHandle(test_func, wrapped_func, given_kwargs) # type: ignore
222
266
 
223
- return wrapped_test
267
+ return wrapped_test if "self" in sig.parameters else wrapped_func
224
268
 
225
269
  return wrapper
226
270
 
@@ -287,5 +331,7 @@ def get_fixtures(func: Callable, request: FixtureRequest, given_kwargs: dict[str
287
331
  """Load fixtures, needed for the test function."""
288
332
  sig = signature(func)
289
333
  return {
290
- name: request.getfixturevalue(name) for name in sig.parameters if name != "case" and name not in given_kwargs
334
+ name: request.getfixturevalue(name)
335
+ for name in sig.parameters
336
+ if name not in ("case", "self") and name not in given_kwargs
291
337
  }
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- import asyncio
3
+ import inspect
4
4
  import unittest
5
5
  from functools import partial
6
6
  from typing import TYPE_CHECKING, Any, Callable, Generator, Type, cast
@@ -172,7 +172,7 @@ class SchemathesisCase(PyCollector):
172
172
  as_strategy_kwargs=as_strategy_kwargs,
173
173
  ),
174
174
  )
175
- if asyncio.iscoroutinefunction(self.test_function):
175
+ if inspect.iscoroutinefunction(self.test_function):
176
176
  # `pytest-trio` expects a coroutine function
177
177
  if is_trio_test:
178
178
  funcobj.hypothesis.inner_test = self.test_function # type: ignore
@@ -17,7 +17,6 @@ from schemathesis.core.control import SkipTest
17
17
  from schemathesis.core.errors import SERIALIZERS_SUGGESTION_MESSAGE, SerializationNotPossible
18
18
  from schemathesis.core.jsonschema.types import JsonSchema
19
19
  from schemathesis.core.parameters import ParameterLocation
20
- from schemathesis.core.transforms import deepclone
21
20
  from schemathesis.core.transport import prepare_urlencoded
22
21
  from schemathesis.generation.meta import (
23
22
  CaseMetadata,
@@ -29,7 +28,7 @@ from schemathesis.generation.meta import (
29
28
  StatefulPhaseData,
30
29
  TestPhase,
31
30
  )
32
- from schemathesis.openapi.generation.filters import is_valid_header, is_valid_path, is_valid_query, is_valid_urlencoded
31
+ from schemathesis.openapi.generation.filters import is_valid_urlencoded
33
32
  from schemathesis.schemas import APIOperation
34
33
  from schemathesis.specs.openapi.adapter.parameters import OpenApiBody, OpenApiParameterSet
35
34
 
@@ -116,7 +115,9 @@ def openapi_cases(
116
115
  else:
117
116
  candidates = operation.body.items
118
117
  parameter = draw(st.sampled_from(candidates))
119
- strategy = _get_body_strategy(parameter, strategy_factory, operation, generation_config, draw)
118
+ strategy = _get_body_strategy(
119
+ parameter, strategy_factory, operation, generation_config, draw, body_generator
120
+ )
120
121
  strategy = apply_hooks(operation, ctx, hooks, strategy, ParameterLocation.BODY)
121
122
  # Parameter may have a wildcard media type. In this case, choose any supported one
122
123
  possible_media_types = sorted(
@@ -209,21 +210,14 @@ def _get_body_strategy(
209
210
  operation: APIOperation,
210
211
  generation_config: GenerationConfig,
211
212
  draw: st.DrawFn,
213
+ generation_mode: GenerationMode,
212
214
  ) -> st.SearchStrategy:
213
- from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
214
-
215
215
  if parameter.media_type in MEDIA_TYPES:
216
216
  return MEDIA_TYPES[parameter.media_type]
217
- schema = parameter.optimized_schema
218
- assert isinstance(operation.schema, BaseOpenAPISchema)
219
- strategy = strategy_factory(
220
- schema,
221
- operation.label,
222
- ParameterLocation.BODY,
223
- parameter.media_type,
224
- generation_config,
225
- operation.schema.adapter.jsonschema_validator_cls,
226
- )
217
+
218
+ # Use the cached strategy from the parameter
219
+ strategy = parameter.get_strategy(operation, generation_config, generation_mode)
220
+
227
221
  # It is likely will be rejected, hence choose it rarely
228
222
  if (
229
223
  not parameter.is_required
@@ -241,7 +235,7 @@ def get_parameters_value(
241
235
  operation: APIOperation,
242
236
  ctx: HookContext,
243
237
  hooks: HookDispatcher | None,
244
- strategy_factory: StrategyFactory,
238
+ generation_mode: GenerationMode,
245
239
  generation_config: GenerationConfig,
246
240
  ) -> dict[str, Any] | None:
247
241
  """Get the final value for the specified location.
@@ -250,10 +244,10 @@ def get_parameters_value(
250
244
  generate those parts.
251
245
  """
252
246
  if value is None:
253
- strategy = get_parameters_strategy(operation, strategy_factory, location, generation_config)
247
+ strategy = get_parameters_strategy(operation, generation_mode, location, generation_config)
254
248
  strategy = apply_hooks(operation, ctx, hooks, strategy, location)
255
249
  return draw(strategy)
256
- strategy = get_parameters_strategy(operation, strategy_factory, location, generation_config, exclude=value.keys())
250
+ strategy = get_parameters_strategy(operation, generation_mode, location, generation_config, exclude=value.keys())
257
251
  strategy = apply_hooks(operation, ctx, hooks, strategy, location)
258
252
  new = draw(strategy)
259
253
  if new is not None:
@@ -304,11 +298,8 @@ def generate_parameter(
304
298
  ):
305
299
  # If we can't negate any parameter, generate positive ones
306
300
  # If nothing else will be negated, then skip the test completely
307
- strategy_factory = make_positive_strategy
308
301
  generator = GenerationMode.POSITIVE
309
- else:
310
- strategy_factory = GENERATOR_MODE_TO_STRATEGY_FACTORY[generator]
311
- value = get_parameters_value(explicit, location, draw, operation, ctx, hooks, strategy_factory, generation_config)
302
+ value = get_parameters_value(explicit, location, draw, operation, ctx, hooks, generator, generation_config)
312
303
  used_generator: GenerationMode | None = generator
313
304
  if value == explicit:
314
305
  # When we pass `explicit`, then its parts are excluded from generation of the final value
@@ -335,86 +326,22 @@ def can_negate_headers(operation: APIOperation, location: ParameterLocation) ->
335
326
  headers = container.schema["properties"]
336
327
  if not headers:
337
328
  return True
338
- return any(header != {"type": "string"} for header in headers.values())
339
-
340
-
341
- def get_schema_for_location(location: ParameterLocation, parameters: OpenApiParameterSet) -> dict[str, Any]:
342
- schema = deepclone(parameters.schema)
343
- if location == ParameterLocation.PATH:
344
- schema["required"] = list(schema["properties"])
345
- # Shallow copy properties dict itself and each modified property
346
- properties = schema.get("properties", {})
347
- if properties:
348
- schema["properties"] = {
349
- key: {**value, "minLength": value.get("minLength", 1)}
350
- if value.get("type") == "string" and "minLength" not in value
351
- else value
352
- for key, value in properties.items()
353
- }
354
- return schema
329
+ return any(
330
+ header not in ({"type": "string"}, {"type": "string", "format": HEADER_FORMAT}) for header in headers.values()
331
+ )
355
332
 
356
333
 
357
334
  def get_parameters_strategy(
358
335
  operation: APIOperation,
359
- strategy_factory: StrategyFactory,
336
+ generation_mode: GenerationMode,
360
337
  location: ParameterLocation,
361
338
  generation_config: GenerationConfig,
362
339
  exclude: Iterable[str] = (),
363
340
  ) -> st.SearchStrategy:
364
341
  """Create a new strategy for the case's component from the API operation parameters."""
365
- from schemathesis.specs.openapi.schemas import BaseOpenAPISchema
366
-
367
342
  container = getattr(operation, location.container_name)
368
343
  if container:
369
- schema = get_schema_for_location(location, container)
370
- if location == ParameterLocation.HEADER and exclude:
371
- # Remove excluded headers case-insensitively
372
- exclude_lower = {name.lower() for name in exclude}
373
- schema["properties"] = {
374
- key: value for key, value in schema["properties"].items() if key.lower() not in exclude_lower
375
- }
376
- if "required" in schema:
377
- schema["required"] = [key for key in schema["required"] if key.lower() not in exclude_lower]
378
- elif exclude:
379
- # Non-header locations: remove by exact name
380
- schema = dict(schema)
381
- schema["properties"] = {key: value for key, value in schema["properties"].items() if key not in exclude}
382
- if "required" in schema:
383
- schema["required"] = [key for key in schema["required"] if key not in exclude]
384
- if not schema["properties"] and strategy_factory is make_negative_strategy:
385
- # Nothing to negate - all properties were excluded
386
- strategy = st.none()
387
- else:
388
- assert isinstance(operation.schema, BaseOpenAPISchema)
389
- strategy = strategy_factory(
390
- schema,
391
- operation.label,
392
- location,
393
- None,
394
- generation_config,
395
- operation.schema.adapter.jsonschema_validator_cls,
396
- )
397
- serialize = operation.get_parameter_serializer(location)
398
- if serialize is not None:
399
- strategy = strategy.map(serialize)
400
- filter_func = {
401
- ParameterLocation.PATH: is_valid_path,
402
- ParameterLocation.HEADER: is_valid_header,
403
- ParameterLocation.COOKIE: is_valid_header,
404
- ParameterLocation.QUERY: is_valid_query,
405
- }[location]
406
- # Headers with special format do not need filtration
407
- if not (location.is_in_header and _can_skip_header_filter(schema)):
408
- strategy = strategy.filter(filter_func)
409
- # Path & query parameters will be cast to string anyway, but having their JSON equivalents for
410
- # `True` / `False` / `None` improves chances of them passing validation in apps
411
- # that expect boolean / null types
412
- # and not aware of Python-specific representation of those types
413
- if location == ParameterLocation.PATH:
414
- strategy = strategy.map(quote_all).map(jsonify_python_specific_types)
415
- elif location == ParameterLocation.QUERY:
416
- strategy = strategy.map(jsonify_python_specific_types)
417
- return strategy
344
+ return container.get_strategy(operation, generation_config, generation_mode, exclude)
418
345
  # No parameters defined for this location
419
346
  return st.none()
420
347
 
@@ -464,13 +391,6 @@ def make_positive_strategy(
464
391
  validator_cls: type[jsonschema.protocols.Validator],
465
392
  ) -> st.SearchStrategy:
466
393
  """Strategy for generating values that fit the schema."""
467
- if location.is_in_header and isinstance(schema, dict):
468
- # We try to enforce the right header values via "format"
469
- # This way, only allowed values will be used during data generation, which reduces the amount of filtering later
470
- # If a property schema contains `pattern` it leads to heavy filtering and worse performance - therefore, skip it
471
- for sub_schema in schema.get("properties", {}).values():
472
- if list(sub_schema) == ["type"] and sub_schema["type"] == "string":
473
- sub_schema.setdefault("format", HEADER_FORMAT)
474
394
  custom_formats = _build_custom_formats(generation_config)
475
395
  return from_schema(
476
396
  schema,