schemathesis 3.39.14__py3-none-any.whl → 3.39.16__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.
schemathesis/__init__.py CHANGED
@@ -6,6 +6,7 @@ from . import auths, checks, contrib, experimental, fixups, graphql, hooks, runn
6
6
  from ._lazy_import import lazy_import
7
7
  from .constants import SCHEMATHESIS_VERSION
8
8
  from .generation import DataGenerationMethod, GenerationConfig, HeaderConfig
9
+ from .hooks import HookContext
9
10
  from .models import Case
10
11
  from .specs import openapi
11
12
 
@@ -65,6 +66,7 @@ __all__ = [
65
66
  "register_check",
66
67
  "register_target",
67
68
  "register_string_format",
69
+ "HookContext",
68
70
  ]
69
71
 
70
72
 
@@ -74,7 +76,13 @@ def _load_generic_response() -> Any:
74
76
  return GenericResponse
75
77
 
76
78
 
77
- _imports = {"GenericResponse": _load_generic_response}
79
+ def _load_base_schema() -> Any:
80
+ from .schemas import BaseSchema
81
+
82
+ return BaseSchema
83
+
84
+
85
+ _imports = {"GenericResponse": _load_generic_response, "BaseSchema": _load_base_schema}
78
86
 
79
87
 
80
88
  def __getattr__(name: str) -> Any:
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any, Callable, Generator, Mapping
11
11
 
12
12
  import hypothesis
13
13
  from hypothesis import Phase
14
+ from hypothesis._settings import all_settings
14
15
  from hypothesis.errors import HypothesisWarning, Unsatisfiable
15
16
  from hypothesis.internal.entropy import deterministic_PRNG
16
17
  from jsonschema.exceptions import SchemaError
@@ -101,7 +102,11 @@ def create_test(
101
102
  default = hypothesis.settings.default
102
103
  wrapped_test._hypothesis_internal_use_settings = hypothesis.settings(
103
104
  wrapped_test._hypothesis_internal_use_settings,
104
- **{item: value for item, value in settings.__dict__.items() if value != getattr(default, item)},
105
+ **{
106
+ item: getattr(settings, item)
107
+ for item in all_settings
108
+ if getattr(settings, item) != getattr(default, item)
109
+ },
105
110
  )
106
111
  else:
107
112
  wrapped_test = settings(wrapped_test)
@@ -150,7 +150,7 @@ class CoverageContext:
150
150
 
151
151
  def is_valid_for_location(self, value: Any) -> bool:
152
152
  if self.location in ("header", "cookie") and isinstance(value, str):
153
- return is_latin_1_encodable(value) and not has_invalid_characters("", value)
153
+ return not value or (is_latin_1_encodable(value) and not has_invalid_characters("", value))
154
154
  return True
155
155
 
156
156
  def generate_from(self, strategy: st.SearchStrategy) -> Any:
@@ -437,6 +437,36 @@ def cover_schema_iter(
437
437
  elif key == "required":
438
438
  template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
439
439
  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)
445
+ k = _to_hashable_key(array_value)
446
+ if k not in seen:
447
+ yield NegativeValue(
448
+ array_value,
449
+ description="Array with more items than allowed by maxItems",
450
+ location=ctx.current_path,
451
+ )
452
+ seen.add(k)
453
+ except (InvalidArgument, Unsatisfiable):
454
+ pass
455
+ elif key == "minItems" and isinstance(value, int) and value > 0:
456
+ try:
457
+ # Force the array to have one less item than the minimum
458
+ new_schema = {**schema, "minItems": value - 1, "maxItems": value - 1, "type": "array"}
459
+ array_value = ctx.generate_from_schema(new_schema)
460
+ k = _to_hashable_key(array_value)
461
+ if k not in seen:
462
+ yield NegativeValue(
463
+ array_value,
464
+ description="Array with fewer items than allowed by minItems",
465
+ location=ctx.current_path,
466
+ )
467
+ seen.add(k)
468
+ except (InvalidArgument, Unsatisfiable):
469
+ pass
440
470
  elif (
441
471
  key == "additionalProperties"
442
472
  and not value
@@ -770,7 +800,12 @@ def _negative_enum(
770
800
  _hashed = _to_hashable_key(x)
771
801
  return _hashed not in seen
772
802
 
773
- strategy = (st.none() | st.booleans() | NUMERIC_STRATEGY | st.text()).filter(is_not_in_value)
803
+ strategy = (
804
+ st.text(alphabet=st.characters(min_codepoint=65, max_codepoint=122, categories=["L"]), min_size=3)
805
+ | st.none()
806
+ | st.booleans()
807
+ | NUMERIC_STRATEGY
808
+ ).filter(is_not_in_value)
774
809
  value = ctx.generate_from(strategy)
775
810
  yield NegativeValue(value, description="Invalid enum value", location=ctx.current_path)
776
811
  hashed = _to_hashable_key(value)
@@ -132,6 +132,7 @@ class OpenAPI20Parameter(OpenAPIParameter):
132
132
  "multipleOf",
133
133
  "example",
134
134
  "examples",
135
+ "default",
135
136
  )
136
137
 
137
138
 
@@ -176,6 +177,7 @@ class OpenAPI30Parameter(OpenAPIParameter):
176
177
  "format",
177
178
  "example",
178
179
  "examples",
180
+ "default",
179
181
  )
180
182
 
181
183
  def from_open_api_to_json_schema(self, operation: APIOperation, open_api_schema: dict[str, Any]) -> dict[str, Any]:
@@ -228,6 +230,7 @@ class OpenAPI20Body(OpenAPIBody, OpenAPI20Parameter):
228
230
  "additionalProperties",
229
231
  "example",
230
232
  "examples",
233
+ "default",
231
234
  )
232
235
  # NOTE. For Open API 2.0 bodies, we still give `x-example` precedence over the schema-level `example` field to keep
233
236
  # the precedence rules consistent.
@@ -141,7 +141,10 @@ class ConvertingResolver(InliningResolver):
141
141
  def resolve(self, ref: str) -> tuple[str, Any]:
142
142
  url, document = super().resolve(ref)
143
143
  document = to_json_schema_recursive(
144
- document, nullable_name=self.nullable_name, is_response_schema=self.is_response_schema
144
+ document,
145
+ nullable_name=self.nullable_name,
146
+ is_response_schema=self.is_response_schema,
147
+ update_quantifiers=False,
145
148
  )
146
149
  return url, document
147
150
 
@@ -1013,24 +1013,30 @@ class SwaggerV20(BaseOpenAPISchema):
1013
1013
  content_types = self.get_request_payload_content_types(operation)
1014
1014
  is_multipart = "multipart/form-data" in content_types
1015
1015
 
1016
- def add_file(file_value: Any) -> None:
1017
- if isinstance(file_value, list):
1018
- for item in file_value:
1019
- files.append((name, (None, item)))
1020
- else:
1021
- files.append((name, file_value))
1016
+ known_fields: dict[str, dict] = {}
1022
1017
 
1023
1018
  for parameter in operation.body:
1024
1019
  if isinstance(parameter, OpenAPI20CompositeBody):
1025
1020
  for form_parameter in parameter.definition:
1026
- name = form_parameter.name
1027
- # It might be not in `form_data`, if the parameter is optional
1028
- if name in form_data:
1029
- value = form_data[name]
1030
- if form_parameter.definition.get("type") == "file" or is_multipart:
1031
- add_file(value)
1032
- else:
1033
- data[name] = value
1021
+ known_fields[form_parameter.name] = form_parameter.definition
1022
+
1023
+ def add_file(name: str, value: Any) -> None:
1024
+ if isinstance(value, list):
1025
+ for item in value:
1026
+ files.append((name, (None, item)))
1027
+ else:
1028
+ files.append((name, value))
1029
+
1030
+ for name, value in form_data.items():
1031
+ param_def = known_fields.get(name)
1032
+ if param_def:
1033
+ if param_def.get("type") == "file" or is_multipart:
1034
+ add_file(name, value)
1035
+ else:
1036
+ data[name] = value
1037
+ else:
1038
+ # Unknown field — treat it as a file (safe default under multipart/form-data)
1039
+ add_file(name, value)
1034
1040
  # `None` is the default value for `files` and `data` arguments in `requests.request`
1035
1041
  return files or None, data or None
1036
1042
 
@@ -1175,7 +1181,11 @@ class OpenApi30(SwaggerV20):
1175
1181
  files = []
1176
1182
  definition = operation.definition.raw
1177
1183
  if "$ref" in definition["requestBody"]:
1178
- body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
1184
+ self.resolver.push_scope(operation.definition.scope)
1185
+ try:
1186
+ body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
1187
+ finally:
1188
+ self.resolver.pop_scope()
1179
1189
  else:
1180
1190
  body = definition["requestBody"]
1181
1191
  content = body["content"]
@@ -1188,14 +1198,19 @@ class OpenApi30(SwaggerV20):
1188
1198
  break
1189
1199
  else:
1190
1200
  raise InternalError("No 'multipart/form-data' media type found in the schema")
1191
- for name, property_schema in (schema or {}).get("properties", {}).items():
1192
- if name in form_data:
1193
- if isinstance(form_data[name], list):
1194
- files.extend([(name, item) for item in form_data[name]])
1201
+ for name, value in form_data.items():
1202
+ property_schema = (schema or {}).get("properties", {}).get(name)
1203
+ if property_schema:
1204
+ if isinstance(value, list):
1205
+ files.extend([(name, item) for item in value])
1195
1206
  elif property_schema.get("format") in ("binary", "base64"):
1196
- files.append((name, form_data[name]))
1207
+ files.append((name, value))
1197
1208
  else:
1198
- files.append((name, (None, form_data[name])))
1209
+ files.append((name, (None, value)))
1210
+ elif isinstance(value, list):
1211
+ files.extend([(name, item) for item in value])
1212
+ else:
1213
+ files.append((name, (None, value)))
1199
1214
  # `None` is the default value for `files` and `data` arguments in `requests.request`
1200
1215
  return files or None, None
1201
1216
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 3.39.14
3
+ Version: 3.39.16
4
4
  Summary: Property-based testing framework for Open API and GraphQL based apps
5
5
  Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
6
6
  Project-URL: Changelog, https://schemathesis.readthedocs.io/en/stable/changelog.html
@@ -1,7 +1,7 @@
1
- schemathesis/__init__.py,sha256=UW2Bq8hDDkcBeAAA7PzpBFXkOOxkmHox-mfQwzHDjL0,1914
1
+ schemathesis/__init__.py,sha256=AGpMI329waZiHwU05CW7cB1cxqQsXvRPB5vs4N7ktB4,2090
2
2
  schemathesis/_compat.py,sha256=y4RZd59i2NCnZ91VQhnKeMn_8t3SgvLOk2Xm8nymUHY,1837
3
3
  schemathesis/_dependency_versions.py,sha256=pjEkkGAfOQJYNb-9UOo84V8nj_lKHr_TGDVdFwY2UU0,816
4
- schemathesis/_hypothesis.py,sha256=CEfWX38CsPy-RzwMGdKuJD9mY_AV8fIq_ZhabGp4tW0,30759
4
+ schemathesis/_hypothesis.py,sha256=L0cCrVk4NBI4Tn9pRIQe_URPXsJCn5Ooay0VHbdj5z8,30899
5
5
  schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
6
6
  schemathesis/_override.py,sha256=TAjYB3eJQmlw9K_xiR9ptt9Wj7if4U7UFlUhGjpBAoM,1625
7
7
  schemathesis/_patches.py,sha256=Hsbpn4UVeXUQD2Kllrbq01CSWsTYENWa0VJTyhX5C2k,895
@@ -61,7 +61,7 @@ schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZE
61
61
  schemathesis/generation/__init__.py,sha256=PClFLK3bu-8Gsy71rgdD0ULMqySrzX-Um8Tan77x_5A,1628
62
62
  schemathesis/generation/_hypothesis.py,sha256=74fzLPHugZgMQXerWYFAMqCAjtAXz5E4gek7Gnkhli4,1756
63
63
  schemathesis/generation/_methods.py,sha256=r8oVlJ71_gXcnEhU-byw2E0R2RswQQFm8U7yGErSqbw,1204
64
- schemathesis/generation/coverage.py,sha256=1CilQSe2DIdMdeWA6RL22so2bZULPRwc0CQBRxcLRFs,39370
64
+ schemathesis/generation/coverage.py,sha256=bE93UnTpp8FsILzbupgqcc-1pQL5Y2DykuIlwWUweM4,41279
65
65
  schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
66
66
  schemathesis/internal/checks.py,sha256=ZPvsPJ7gWwK0IpzBFgOMaq4L2e0yfeC8qxPrnpauVFA,2741
67
67
  schemathesis/internal/copy.py,sha256=DcL56z-d69kKR_5u8mlHvjSL1UTyUKNMAwexrwHFY1s,1031
@@ -116,10 +116,10 @@ schemathesis/specs/openapi/formats.py,sha256=3KtEC-8nQRwMErS-WpMadXsr8R0O-NzYwFi
116
116
  schemathesis/specs/openapi/links.py,sha256=C4Uir2P_EcpqME8ee_a1vdUM8Tm3ZcKNn2YsGjZiMUQ,17935
117
117
  schemathesis/specs/openapi/loaders.py,sha256=jlTYLoG5sVRh8xycIF2M2VDCZ44M80Sct07a_ycg1Po,25698
118
118
  schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
119
- schemathesis/specs/openapi/parameters.py,sha256=X_3PKqUScIiN_vbSFEauPYyxASyFv-_9lZ_9QEZRLqo,14655
119
+ schemathesis/specs/openapi/parameters.py,sha256=fP4BupY_1wFbjL9n0lTtpQZY0YBt2mCjrG598LF8ZOI,14712
120
120
  schemathesis/specs/openapi/patterns.py,sha256=L99UtslPvwObCVf5ndq3vL2YjQ7H1nMb-ZNMcyz_Qvk,12677
121
- schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
122
- schemathesis/specs/openapi/schemas.py,sha256=JA9SiBnwYg75kYnd4_0CWOuQv_XTfYwuDeGmFe4RtVo,53724
121
+ schemathesis/specs/openapi/references.py,sha256=0-gqbAxvBfrvFXA7YqmcNh7zRY7V_pKEnak0ncwQljI,9894
122
+ schemathesis/specs/openapi/schemas.py,sha256=Vrk6ORM1POvMe5XkbYTT9pnLFsDTFvHXrCplyKnBeDU,54173
123
123
  schemathesis/specs/openapi/security.py,sha256=Z-6pk2Ga1PTUtBe298KunjVHsNh5A-teegeso7zcPIE,7138
124
124
  schemathesis/specs/openapi/serialization.py,sha256=rcZfqQbWer_RELedu4Sh5h_RhKYPWTfUjnmLwpP2R_A,11842
125
125
  schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
@@ -153,8 +153,8 @@ schemathesis/transports/auth.py,sha256=urSTO9zgFO1qU69xvnKHPFQV0SlJL3d7_Ojl0tLnZ
153
153
  schemathesis/transports/content_types.py,sha256=MiKOm-Hy5i75hrROPdpiBZPOTDzOwlCdnthJD12AJzI,2187
154
154
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
155
155
  schemathesis/transports/responses.py,sha256=OFD4ZLqwEFpo7F9vaP_SVgjhxAqatxIj38FS4XVq8Qs,1680
156
- schemathesis-3.39.14.dist-info/METADATA,sha256=qprGVLj3roPTw4lckY5h75CP_d76khmUwPa_JRBtv_M,11901
157
- schemathesis-3.39.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
158
- schemathesis-3.39.14.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
159
- schemathesis-3.39.14.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
160
- schemathesis-3.39.14.dist-info/RECORD,,
156
+ schemathesis-3.39.16.dist-info/METADATA,sha256=3G135B_eBxr3JzsGS2GBSk9HrsnaD0CSfo-fUIVpKRM,11901
157
+ schemathesis-3.39.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
158
+ schemathesis-3.39.16.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
159
+ schemathesis-3.39.16.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
160
+ schemathesis-3.39.16.dist-info/RECORD,,