schemathesis 4.0.4__py3-none-any.whl → 4.0.5__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.
@@ -111,8 +111,9 @@ class CoverageContext:
111
111
  location: str
112
112
  is_required: bool
113
113
  path: list[str | int]
114
+ custom_formats: dict[str, st.SearchStrategy]
114
115
 
115
- __slots__ = ("location", "generation_modes", "is_required", "path")
116
+ __slots__ = ("location", "generation_modes", "is_required", "path", "custom_formats")
116
117
 
117
118
  def __init__(
118
119
  self,
@@ -121,11 +122,13 @@ class CoverageContext:
121
122
  generation_modes: list[GenerationMode] | None = None,
122
123
  is_required: bool,
123
124
  path: list[str | int] | None = None,
125
+ custom_formats: dict[str, st.SearchStrategy],
124
126
  ) -> None:
125
127
  self.location = location
126
128
  self.generation_modes = generation_modes if generation_modes is not None else list(GenerationMode)
127
129
  self.is_required = is_required
128
130
  self.path = path or []
131
+ self.custom_formats = custom_formats
129
132
 
130
133
  @contextmanager
131
134
  def at(self, key: str | int) -> Generator[None, None, None]:
@@ -145,6 +148,7 @@ class CoverageContext:
145
148
  generation_modes=[GenerationMode.POSITIVE],
146
149
  is_required=self.is_required,
147
150
  path=self.path,
151
+ custom_formats=self.custom_formats,
148
152
  )
149
153
 
150
154
  def with_negative(self) -> CoverageContext:
@@ -153,6 +157,7 @@ class CoverageContext:
153
157
  generation_modes=[GenerationMode.NEGATIVE],
154
158
  is_required=self.is_required,
155
159
  path=self.path,
160
+ custom_formats=self.custom_formats,
156
161
  )
157
162
 
158
163
  def is_valid_for_location(self, value: Any) -> bool:
@@ -234,7 +239,10 @@ class CoverageContext:
234
239
  return cached_draw(
235
240
  st.lists(
236
241
  st.fixed_dictionaries(
237
- {key: from_schema(sub_schema) for key, sub_schema in items["properties"].items()}
242
+ {
243
+ key: from_schema(sub_schema, custom_formats=self.custom_formats)
244
+ for key, sub_schema in items["properties"].items()
245
+ }
238
246
  ),
239
247
  min_size=min_items,
240
248
  )
@@ -245,7 +253,7 @@ class CoverageContext:
245
253
  if isinstance(schema, dict) and "allOf" not in schema:
246
254
  return self.generate_from_schema(schema)
247
255
 
248
- return self.generate_from(from_schema(schema))
256
+ return self.generate_from(from_schema(schema, custom_formats=self.custom_formats))
249
257
 
250
258
 
251
259
  T = TypeVar("T")
@@ -18,7 +18,7 @@ from requests.models import CaseInsensitiveDict
18
18
 
19
19
  from schemathesis import auths
20
20
  from schemathesis.auths import AuthStorage, AuthStorageMark
21
- from schemathesis.config import ProjectConfig
21
+ from schemathesis.config import GenerationConfig, ProjectConfig
22
22
  from schemathesis.core import NOT_SET, NotSet, SpecificationFeature, media_types
23
23
  from schemathesis.core.errors import InvalidSchema, SerializationNotPossible
24
24
  from schemathesis.core.marks import Mark
@@ -183,6 +183,7 @@ def create_test(
183
183
  config.as_strategy_kwargs,
184
184
  generate_duplicate_query_parameters=phases_config.coverage.generate_duplicate_query_parameters,
185
185
  unexpected_methods=phases_config.coverage.unexpected_methods,
186
+ generation_config=generation,
186
187
  )
187
188
 
188
189
  setattr(hypothesis_test, SETTINGS_ATTRIBUTE_NAME, settings)
@@ -295,7 +296,8 @@ def add_coverage(
295
296
  auth_storage: AuthStorage | None,
296
297
  as_strategy_kwargs: dict[str, Any],
297
298
  generate_duplicate_query_parameters: bool,
298
- unexpected_methods: set[str] | None = None,
299
+ unexpected_methods: set[str],
300
+ generation_config: GenerationConfig,
299
301
  ) -> Callable:
300
302
  from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
301
303
 
@@ -309,7 +311,11 @@ def add_coverage(
309
311
  if container in as_strategy_kwargs
310
312
  }
311
313
  for case in _iter_coverage_cases(
312
- operation, generation_modes, generate_duplicate_query_parameters, unexpected_methods
314
+ operation=operation,
315
+ generation_modes=generation_modes,
316
+ generate_duplicate_query_parameters=generate_duplicate_query_parameters,
317
+ unexpected_methods=unexpected_methods,
318
+ generation_config=generation_config,
313
319
  ):
314
320
  if case.media_type and operation.schema.transport.get_first_matching_media_type(case.media_type) is None:
315
321
  continue
@@ -446,11 +452,14 @@ def _stringify_value(val: Any, container_name: str) -> Any:
446
452
 
447
453
 
448
454
  def _iter_coverage_cases(
455
+ *,
449
456
  operation: APIOperation,
450
457
  generation_modes: list[GenerationMode],
451
458
  generate_duplicate_query_parameters: bool,
452
- unexpected_methods: set[str] | None = None,
459
+ unexpected_methods: set[str],
460
+ generation_config: GenerationConfig,
453
461
  ) -> Generator[Case, None, None]:
462
+ from schemathesis.specs.openapi._hypothesis import _build_custom_formats
454
463
  from schemathesis.specs.openapi.constants import LOCATION_TO_CONTAINER
455
464
  from schemathesis.specs.openapi.examples import find_in_responses, find_matching_in_responses
456
465
  from schemathesis.specs.openapi.serialization import get_serializers_for_operation
@@ -463,6 +472,7 @@ def _iter_coverage_cases(
463
472
  responses = find_in_responses(operation)
464
473
  # NOTE: The HEAD method is excluded
465
474
  unexpected_methods = unexpected_methods or {"get", "put", "post", "delete", "options", "patch", "trace"}
475
+ custom_formats = _build_custom_formats(generation_config)
466
476
 
467
477
  seen_negative = coverage.HashSet()
468
478
  seen_positive = coverage.HashSet()
@@ -475,7 +485,10 @@ def _iter_coverage_cases(
475
485
  schema.setdefault("examples", []).append(value)
476
486
  gen = coverage.cover_schema_iter(
477
487
  coverage.CoverageContext(
478
- location=location, generation_modes=generation_modes, is_required=parameter.is_required
488
+ location=location,
489
+ generation_modes=generation_modes,
490
+ is_required=parameter.is_required,
491
+ custom_formats=custom_formats,
479
492
  ),
480
493
  schema,
481
494
  )
@@ -496,7 +509,10 @@ def _iter_coverage_cases(
496
509
  schema.setdefault("examples", []).extend(examples)
497
510
  gen = coverage.cover_schema_iter(
498
511
  coverage.CoverageContext(
499
- location="body", generation_modes=generation_modes, is_required=body.is_required
512
+ location="body",
513
+ generation_modes=generation_modes,
514
+ is_required=body.is_required,
515
+ custom_formats=custom_formats,
500
516
  ),
501
517
  schema,
502
518
  )
@@ -723,7 +739,10 @@ def _iter_coverage_cases(
723
739
  iterator = iter(
724
740
  coverage.cover_schema_iter(
725
741
  coverage.CoverageContext(
726
- location=_location, generation_modes=[GenerationMode.NEGATIVE], is_required=is_required
742
+ location=_location,
743
+ generation_modes=[GenerationMode.NEGATIVE],
744
+ is_required=is_required,
745
+ custom_formats=custom_formats,
727
746
  ),
728
747
  subschema,
729
748
  )
@@ -330,7 +330,7 @@ class AcceptedNegativeData(Failure):
330
330
  message: str,
331
331
  status_code: int,
332
332
  expected_statuses: list[str],
333
- title: str = "Accepted negative data",
333
+ title: str = "API accepted schema-violating request",
334
334
  case_id: str | None = None,
335
335
  ) -> None:
336
336
  self.operation = operation
@@ -358,7 +358,7 @@ class RejectedPositiveData(Failure):
358
358
  message: str,
359
359
  status_code: int,
360
360
  allowed_statuses: list[str],
361
- title: str = "Rejected positive data",
361
+ title: str = "API rejected schema-compliant request",
362
362
  case_id: str | None = None,
363
363
  ) -> None:
364
364
  self.operation = operation
@@ -423,10 +423,8 @@ def jsonify_python_specific_types(value: dict[str, Any]) -> dict[str, Any]:
423
423
  return value
424
424
 
425
425
 
426
- def _build_custom_formats(
427
- custom_formats: dict[str, st.SearchStrategy] | None, generation_config: GenerationConfig
428
- ) -> dict[str, st.SearchStrategy]:
429
- custom_formats = {**get_default_format_strategies(), **STRING_FORMATS, **(custom_formats or {})}
426
+ def _build_custom_formats(generation_config: GenerationConfig) -> dict[str, st.SearchStrategy]:
427
+ custom_formats = {**get_default_format_strategies(), **STRING_FORMATS}
430
428
  if generation_config.exclude_header_characters is not None:
431
429
  custom_formats[HEADER_FORMAT] = header_values(exclude_characters=generation_config.exclude_header_characters)
432
430
  elif not generation_config.allow_x00:
@@ -441,7 +439,6 @@ def make_positive_strategy(
441
439
  media_type: str | None,
442
440
  generation_config: GenerationConfig,
443
441
  validator_cls: type[jsonschema.protocols.Validator],
444
- custom_formats: dict[str, st.SearchStrategy] | None = None,
445
442
  ) -> st.SearchStrategy:
446
443
  """Strategy for generating values that fit the schema."""
447
444
  if is_header_location(location):
@@ -451,7 +448,7 @@ def make_positive_strategy(
451
448
  for sub_schema in schema.get("properties", {}).values():
452
449
  if list(sub_schema) == ["type"] and sub_schema["type"] == "string":
453
450
  sub_schema.setdefault("format", HEADER_FORMAT)
454
- custom_formats = _build_custom_formats(custom_formats, generation_config)
451
+ custom_formats = _build_custom_formats(generation_config)
455
452
  return from_schema(
456
453
  schema,
457
454
  custom_formats=custom_formats,
@@ -472,9 +469,8 @@ def make_negative_strategy(
472
469
  media_type: str | None,
473
470
  generation_config: GenerationConfig,
474
471
  validator_cls: type[jsonschema.protocols.Validator],
475
- custom_formats: dict[str, st.SearchStrategy] | None = None,
476
472
  ) -> st.SearchStrategy:
477
- custom_formats = _build_custom_formats(custom_formats, generation_config)
473
+ custom_formats = _build_custom_formats(generation_config)
478
474
  return negative_schema(
479
475
  schema,
480
476
  operation_name=operation_name,
@@ -240,7 +240,7 @@ def negative_data_rejection(ctx: CheckContext, response: Response, case: Case) -
240
240
  ):
241
241
  raise AcceptedNegativeData(
242
242
  operation=case.operation.label,
243
- message=f"Allowed statuses: {', '.join(config.expected_statuses)}",
243
+ message=f"Invalid data should have been rejected\nExpected: {', '.join(config.expected_statuses)}",
244
244
  status_code=response.status_code,
245
245
  expected_statuses=config.expected_statuses,
246
246
  )
@@ -264,7 +264,7 @@ def positive_data_acceptance(ctx: CheckContext, response: Response, case: Case)
264
264
  if case.meta.generation.mode.is_positive and response.status_code not in allowed_statuses:
265
265
  raise RejectedPositiveData(
266
266
  operation=case.operation.label,
267
- message=f"Allowed statuses: {', '.join(config.expected_statuses)}",
267
+ message=f"Valid data should have been accepted\nExpected: {', '.join(config.expected_statuses)}",
268
268
  status_code=response.status_code,
269
269
  allowed_statuses=config.expected_statuses,
270
270
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.0.4
3
+ Version: 4.0.5
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
@@ -85,13 +85,13 @@ schemathesis/engine/phases/unit/_executor.py,sha256=9MmZoKSBVSPk0LWwN3PZ3iaO9nzp
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=MuqnKsJBpGm2gaqDFdJi1yGSWgBhqJUwtYaX97kfXgo,11820
88
- schemathesis/generation/coverage.py,sha256=SlPD8WfrRXca4A9p6P894JXBAqjdCVAto0V4qQrceOE,48825
88
+ schemathesis/generation/coverage.py,sha256=gf3kBAQAM5SoUS2k5e4UYIw-w6yvBtm_9KmdQyrhiss,49253
89
89
  schemathesis/generation/meta.py,sha256=adkoMuCfzSjHJ9ZDocQn0GnVldSCkLL3eVR5A_jafwM,2552
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
93
  schemathesis/generation/hypothesis/__init__.py,sha256=SVwM-rx07jPZzms0idWYACgUtWAxh49HRuTnaQ__zf0,1549
94
- schemathesis/generation/hypothesis/builder.py,sha256=JtKh9hzob2byQrAtf0IXOgKX1c17mLiMQ8f030Hae2Y,33414
94
+ schemathesis/generation/hypothesis/builder.py,sha256=CURcKG1JamDNwMgZysMaqL5Mrbj0Hx3dgAxJHaB1ZAg,34102
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
@@ -102,7 +102,7 @@ schemathesis/graphql/__init__.py,sha256=_eO6MAPHGgiADVGRntnwtPxmuvk666sAh-FAU4cG
102
102
  schemathesis/graphql/checks.py,sha256=IADbxiZjgkBWrC5yzHDtohRABX6zKXk5w_zpWNwdzYo,3186
103
103
  schemathesis/graphql/loaders.py,sha256=KarhFo2lDiC_1GcC2UGHl_MRDWAMMMWtE7sKrdbaJvo,9348
104
104
  schemathesis/openapi/__init__.py,sha256=-KcsSAM19uOM0N5J4s-yTnQ1BFsptYhW1E51cEf6kVM,311
105
- schemathesis/openapi/checks.py,sha256=CQ9IjySb7wXJkbv5K0eUKZ0iU1LW--fbfN-wD6ayOV8,12389
105
+ schemathesis/openapi/checks.py,sha256=VaQRxko6KwZL6saIzc4uUgJa_fj086O7Y6QFK8Zg-7A,12419
106
106
  schemathesis/openapi/loaders.py,sha256=1jh4Me2dngWalBmX5xjfln90tfbH-afxmjsiY1BuUTE,10645
107
107
  schemathesis/openapi/generation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
108
108
  schemathesis/openapi/generation/filters.py,sha256=pY9cUZdL_kQK80Z2aylTOqqa12zmaYUlYC5BfYgeQMk,2395
@@ -123,8 +123,8 @@ schemathesis/specs/graphql/schemas.py,sha256=ezkqgMwx37tMWlhy_I0ahDF1Q44emDSJkyj
123
123
  schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzVfHxtdLkCPKM,1422
124
124
  schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
125
125
  schemathesis/specs/openapi/_cache.py,sha256=HpglmETmZU0RCHxp3DO_sg5_B_nzi54Zuw9vGzzYCxY,4295
126
- schemathesis/specs/openapi/_hypothesis.py,sha256=NuXucpwn8jjL_O0anja1TPqEuXyuFq8quIuECIW9BLY,22611
127
- schemathesis/specs/openapi/checks.py,sha256=mKJ-ZkbjhbXS4eWDZiv8zslXKFDqkE3Mp4N8TVDHiI0,29801
126
+ schemathesis/specs/openapi/_hypothesis.py,sha256=usufzl_VyBLgI6riTZ-pGqKnSLndw89GRIuCgCH9QiY,22366
127
+ schemathesis/specs/openapi/checks.py,sha256=0YiMoUy_wsnPvbOrsbnQ2iDxLloNe2-dc5-hnsst0ss,29863
128
128
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
129
129
  schemathesis/specs/openapi/converter.py,sha256=lil8IewM5j8tvt4lpA9g_KITvIwx1M96i45DNSHNjoc,3505
130
130
  schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
@@ -157,8 +157,8 @@ schemathesis/transport/prepare.py,sha256=iiB8KTAqnnuqjWzblIPiGVdkGIF7Yr1SAEz-KZz
157
157
  schemathesis/transport/requests.py,sha256=rziZTrZCVMAqgy6ldB8iTwhkpAsnjKSgK8hj5Sq3ThE,10656
158
158
  schemathesis/transport/serialization.py,sha256=igUXKZ_VJ9gV7P0TUc5PDQBJXl_s0kK9T3ljGWWvo6E,10339
159
159
  schemathesis/transport/wsgi.py,sha256=KoAfvu6RJtzyj24VGB8e-Iaa9smpgXJ3VsM8EgAz2tc,6152
160
- schemathesis-4.0.4.dist-info/METADATA,sha256=rRUbFXvyFsf72j7N6smvPu4mMvQYJLnDCvjPwf3XPOY,8471
161
- schemathesis-4.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
- schemathesis-4.0.4.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
- schemathesis-4.0.4.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
- schemathesis-4.0.4.dist-info/RECORD,,
160
+ schemathesis-4.0.5.dist-info/METADATA,sha256=cIno0cKRp1ZT1ttNzK70yTrErPrGv7479Pc7dRTtDxI,8471
161
+ schemathesis-4.0.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
+ schemathesis-4.0.5.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
+ schemathesis-4.0.5.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
+ schemathesis-4.0.5.dist-info/RECORD,,