schemathesis 4.3.15__py3-none-any.whl → 4.3.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.

Potentially problematic release.


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

schemathesis/checks.py CHANGED
@@ -93,7 +93,7 @@ CHECKS = Registry[CheckFunction]()
93
93
 
94
94
  def load_all_checks() -> None:
95
95
  # NOTE: Trigger registering all Open API checks
96
- from schemathesis.specs.openapi.checks import status_code_conformance # noqa: F401, F403
96
+ from schemathesis.specs.openapi.checks import status_code_conformance # noqa: F401
97
97
 
98
98
 
99
99
  def check(func: CheckFunction) -> CheckFunction:
@@ -123,8 +123,7 @@ def vcr_writer(output: TextOutput, config: ProjectConfig, queue: Queue) -> None:
123
123
  current_id = 1
124
124
 
125
125
  def write_header_values(stream: IO, values: list[str]) -> None:
126
- for v in values:
127
- stream.write(f" - {json.dumps(v)}\n")
126
+ stream.writelines(f" - {json.dumps(v)}\n" for v in values)
128
127
 
129
128
  if config.output.sanitization.enabled:
130
129
  sanitization_keys = config.output.sanitization.keys_to_sanitize
@@ -142,7 +142,7 @@ def _format_anyof_error(error: ValidationError) -> str:
142
142
  )
143
143
  elif list(error.schema_path) == ["properties", "workers", "anyOf"]:
144
144
  return (
145
- f"Invalid value for 'workers': {repr(error.instance)}\n\n"
145
+ f"Invalid value for 'workers': {error.instance!r}\n\n"
146
146
  f"Expected either:\n"
147
147
  f" - A positive integer (e.g., workers = 4)\n"
148
148
  f' - The string "auto" for automatic detection (workers = "auto")'
@@ -161,6 +161,8 @@ class InvalidSchema(SchemathesisError):
161
161
  message += "\n File reference could not be resolved. Check that the file exists."
162
162
  elif reference.startswith(("#/components", "#/definitions")):
163
163
  message += "\n Component does not exist in the schema."
164
+ elif isinstance(error.__cause__, RemoteDocumentError):
165
+ message += f"\n {error.__cause__}"
164
166
  return cls(message, path=path, method=method)
165
167
 
166
168
  def as_failing_test_function(self) -> Callable:
@@ -176,6 +178,13 @@ class InvalidSchema(SchemathesisError):
176
178
  return actual_test
177
179
 
178
180
 
181
+ class RemoteDocumentError(SchemathesisError):
182
+ """Remote reference resolution failed.
183
+
184
+ This exception carries more context than the default one in `jsonschema`.
185
+ """
186
+
187
+
179
188
  class HookError(SchemathesisError):
180
189
  """Happens during hooks loading."""
181
190
 
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- from itertools import chain
4
3
  from typing import Any, Callable, overload
5
4
 
6
5
  from schemathesis.core.jsonschema.bundler import BUNDLE_STORAGE_KEY
@@ -188,29 +187,17 @@ def update_pattern_in_schema(schema: dict[str, Any]) -> None:
188
187
 
189
188
  def rewrite_properties(schema: dict[str, Any], predicate: Callable[[dict[str, Any]], bool]) -> None:
190
189
  required = schema.get("required", [])
191
- forbidden = []
192
190
  for name, subschema in list(schema.get("properties", {}).items()):
193
191
  if predicate(subschema):
194
192
  if name in required:
195
193
  required.remove(name)
196
- del schema["properties"][name]
197
- forbidden.append(name)
198
- if forbidden:
199
- forbid_properties(schema, forbidden)
194
+ schema["properties"][name] = {"not": {}}
200
195
  if not schema.get("required"):
201
196
  schema.pop("required", None)
202
197
  if not schema.get("properties"):
203
198
  schema.pop("properties", None)
204
199
 
205
200
 
206
- def forbid_properties(schema: dict[str, Any], forbidden: list[str]) -> None:
207
- """Explicitly forbid properties via the `not` keyword."""
208
- not_schema = schema.setdefault("not", {})
209
- already_forbidden = not_schema.setdefault("required", [])
210
- already_forbidden.extend(forbidden)
211
- not_schema["required"] = list(set(chain(already_forbidden, forbidden)))
212
-
213
-
214
201
  def is_write_only(schema: dict[str, Any] | bool) -> bool:
215
202
  if isinstance(schema, bool):
216
203
  return False
@@ -242,7 +242,7 @@ def _expand_subschemas(
242
242
  except InfiniteRecursiveReference:
243
243
  return
244
244
 
245
- yield (schema, current_path)
245
+ yield schema, current_path
246
246
 
247
247
  if isinstance(schema, dict):
248
248
  # For anyOf/oneOf, yield each alternative with the same path
@@ -250,10 +250,10 @@ def _expand_subschemas(
250
250
  if key in schema:
251
251
  for subschema in schema[key]:
252
252
  # Each alternative starts with the current path
253
- yield (subschema, current_path)
253
+ yield subschema, current_path
254
254
 
255
255
  # For allOf, merge all alternatives
256
- if "allOf" in schema and schema["allOf"]:
256
+ if schema.get("allOf"):
257
257
  subschema = deepclone(schema["allOf"][0])
258
258
  try:
259
259
  subschema, expanded_path = _resolve_bundled(subschema, resolver, current_path)
@@ -278,7 +278,7 @@ def _expand_subschemas(
278
278
  else:
279
279
  subschema[key] = value
280
280
 
281
- yield (subschema, expanded_path)
281
+ yield subschema, expanded_path
282
282
 
283
283
 
284
284
  def extract_inner_examples(examples: dict[str, Any] | list, schema: BaseOpenAPISchema) -> Generator[Any, None, None]:
@@ -9,6 +9,7 @@ import requests
9
9
 
10
10
  from schemathesis.core.compat import RefResolutionError, RefResolver
11
11
  from schemathesis.core.deserialization import deserialize_yaml
12
+ from schemathesis.core.errors import RemoteDocumentError
12
13
  from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
13
14
 
14
15
 
@@ -30,10 +31,39 @@ def load_file_uri(location: str) -> dict[str, Any]:
30
31
  return load_file_impl(location, urlopen)
31
32
 
32
33
 
34
+ _HTML_MARKERS = (b"<!doctype", b"<html", b"<head", b"<body")
35
+
36
+
37
+ def _looks_like_html(content_type: str | None, body: bytes) -> bool:
38
+ if content_type and "html" in content_type.lower():
39
+ return True
40
+ head = body.lstrip()[:64].lower()
41
+ return any(head.startswith(m) for m in _HTML_MARKERS)
42
+
43
+
33
44
  def load_remote_uri(uri: str) -> Any:
34
45
  """Load the resource and parse it as YAML / JSON."""
35
46
  response = requests.get(uri, timeout=DEFAULT_RESPONSE_TIMEOUT)
36
- return deserialize_yaml(response.content)
47
+ content_type = response.headers.get("Content-Type", "")
48
+ body = response.content or b""
49
+
50
+ def _suffix() -> str:
51
+ return f"(HTTP {response.status_code}, Content-Type={content_type}, size={len(body)})"
52
+
53
+ if not (200 <= response.status_code < 300):
54
+ raise RemoteDocumentError(f"Failed to fetch {_suffix()}")
55
+
56
+ if _looks_like_html(content_type, body):
57
+ raise RemoteDocumentError(f"Expected YAML/JSON, got HTML {_suffix()}")
58
+
59
+ document = deserialize_yaml(response.content)
60
+
61
+ if not isinstance(document, (dict, list)):
62
+ raise RemoteDocumentError(
63
+ f"Remote document is parsed as {type(document).__name__}, but an object/array is expected {_suffix()}"
64
+ )
65
+
66
+ return document
37
67
 
38
68
 
39
69
  JSONType = Union[None, bool, float, str, list, Dict[str, Any]]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.3.15
3
+ Version: 4.3.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://github.com/schemathesis/schemathesis/blob/master/CHANGELOG.md
@@ -1,6 +1,6 @@
1
1
  schemathesis/__init__.py,sha256=QqVUCBQr-RDEstgCZLsxzIa9HJslVSeijrm9gES4b_0,1423
2
2
  schemathesis/auths.py,sha256=JdEwPRS9WKmPcxzGXYYz9pjlIUMQYCfif7ZJU0Kde-I,16400
3
- schemathesis/checks.py,sha256=QaG8ErFCOSYe1GqJKjoiOesv2oZgFTedRUhBZUpTgR8,6879
3
+ schemathesis/checks.py,sha256=F_lsC5JTUKm_ByvilBN_9IpbL4mJiidfLgS8ir2ZDoQ,6873
4
4
  schemathesis/errors.py,sha256=K3irHIZkrBH2-9LIjlgXlm8RNC41Nffd39ncfwagUvw,1053
5
5
  schemathesis/filters.py,sha256=IevPA5A04GfRLLjmkFLZ0CLhjNO3RmpZq_yw6MqjLIA,13515
6
6
  schemathesis/hooks.py,sha256=q2wqYNgpMCO8ImSBkbrWDSwN0BSELelqJMgAAgGvv2M,14836
@@ -21,7 +21,7 @@ schemathesis/cli/commands/run/loaders.py,sha256=eRgP1ZPfhOfxR7iXQ_CfV9r_8jP1DN4t
21
21
  schemathesis/cli/commands/run/validation.py,sha256=DQaMiBLN2tYT9hONvv8xnyPvNXZH768UlOdUxTd5kZs,9193
22
22
  schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31MAXXn1qI7uU4FtiDwroXZI,1915
23
23
  schemathesis/cli/commands/run/handlers/base.py,sha256=qUtDvtr3F6were_BznfnaPpMibGJMnQ5CA9aEzcIUBc,1306
24
- schemathesis/cli/commands/run/handlers/cassettes.py,sha256=LzvQp--Ub5MXF7etet7fQD0Ufloh1R0j2X1o9dT8Z4k,19253
24
+ schemathesis/cli/commands/run/handlers/cassettes.py,sha256=2sXW9jykEFw4HCv25ycRfRd8uw5VV1e26Cp14O7PVhs,19245
25
25
  schemathesis/cli/commands/run/handlers/junitxml.py,sha256=qiFvM4-SlM67sep003SkLqPslzaEb4nOm3bkzw-DO-Q,2602
26
26
  schemathesis/cli/commands/run/handlers/output.py,sha256=pPp5-lJP3Zir1sTA7fmlhc-u1Jn17enXZNUerQMr56M,64166
27
27
  schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -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=U7wuE4480EjP3K16mfC528TP5q7Q5IwAZwZLqRIrS1E,4300
35
35
  schemathesis/config/_env.py,sha256=8XfIyrnGNQuCDnfG0lwmKRFbasRUjgeQGBAMupsmtOU,613
36
- schemathesis/config/_error.py,sha256=jfv9chQ4NGoDYypszNGymr0zxXVo65yP0AWK1WVEPIM,5781
36
+ schemathesis/config/_error.py,sha256=0QnXO7Zagr69aYsX46GHm5xcTDd_18m8pFQBb0SsYvY,5777
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=JvfMkieYBkbEmZRb4cTvQLfvHQLhmsxa3GXzgjOtmFc,12383
@@ -52,7 +52,7 @@ schemathesis/core/compat.py,sha256=9BWCrFoqN2sJIaiht_anxe8kLjYMR7t0iiOkXqLRUZ8,1
52
52
  schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
53
53
  schemathesis/core/curl.py,sha256=jrPL9KpNHteyJ6A1oxJRSkL5bfuBeuPs3xh9Z_ml2cE,1892
54
54
  schemathesis/core/deserialization.py,sha256=qjXUPaz_mc1OSgXzTUSkC8tuVR8wgVQtb9g3CcAF6D0,2951
55
- schemathesis/core/errors.py,sha256=sr23WgbD-52n5fmC-QBn2suzNUbsB1okvXIs_L5EyR0,19918
55
+ schemathesis/core/errors.py,sha256=NxuhMozUnC57BSsTKC95zJgSflZ1d01D3j6p8VyXhcw,20209
56
56
  schemathesis/core/failures.py,sha256=yFpAxWdEnm0Ri8z8RqRI9H7vcLH5ztOeSIi4m4SGx5g,8996
57
57
  schemathesis/core/fs.py,sha256=ItQT0_cVwjDdJX9IiI7EnU75NI2H3_DCEyyUjzg_BgI,472
58
58
  schemathesis/core/hooks.py,sha256=qhbkkRSf8URJ4LKv2wmKRINKpquUOgxQzWBHKWRWo3Q,475
@@ -130,13 +130,13 @@ schemathesis/specs/graphql/validation.py,sha256=-W1Noc1MQmTb4RX-gNXMeU2qkgso4mzV
130
130
  schemathesis/specs/openapi/__init__.py,sha256=C5HOsfuDJGq_3mv8CRBvRvb0Diy1p0BFdqyEXMS-loE,238
131
131
  schemathesis/specs/openapi/_hypothesis.py,sha256=O8vN-koBjzBVZfpD3pmgIt6ecU4ddAPHOxTAORd23Lo,22642
132
132
  schemathesis/specs/openapi/checks.py,sha256=tb2s8azZYcO__Jf13ONUstO1inXoZBlo3dD2uuABB24,31712
133
- schemathesis/specs/openapi/converter.py,sha256=tnh08EDvmac5ONaCUmBwrblpWu43TaQ7qhJZeh5PJIc,6963
133
+ schemathesis/specs/openapi/converter.py,sha256=OlvGCCDAiIZS_osM9OQBvDGzwEToUZmVWwzQa4wQNws,6463
134
134
  schemathesis/specs/openapi/definitions.py,sha256=8htclglV3fW6JPBqs59lgM4LnA25Mm9IptXBPb_qUT0,93949
135
- schemathesis/specs/openapi/examples.py,sha256=uHV1HRqFhwpGNsBWHt7WmehyIyr8d-n-VeKKs4FRt2c,24475
135
+ schemathesis/specs/openapi/examples.py,sha256=bNQVYJAb2LGtl_6josaNj5O4B0b_WaqtXVg1ZTvDDv8,24451
136
136
  schemathesis/specs/openapi/formats.py,sha256=4tYRdckauHxkJCmOhmdwDq_eOpHPaKloi89lzMPbPzw,3975
137
137
  schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKRcUOc41eEwKbo,2472
138
138
  schemathesis/specs/openapi/patterns.py,sha256=GqPZEXMRdWENQxanWjBOalIZ2MQUjuxk21kmdiI703E,18027
139
- schemathesis/specs/openapi/references.py,sha256=AW1laU23BkiRf0EEFM538vyVFLXycGUiucGVV461le0,1927
139
+ schemathesis/specs/openapi/references.py,sha256=Jez2KpfsDIwXSvxZCmRQtlHk2X9UZ-JZkKAJaCPSLk8,2981
140
140
  schemathesis/specs/openapi/schemas.py,sha256=ONFB8kMBrryZL_tKHWvxnBjyUHoHh_MAUqxjuVDc78c,34034
141
141
  schemathesis/specs/openapi/serialization.py,sha256=RPNdadne5wdhsGmjSvgKLRF58wpzpRx3wura8PsHM3o,12152
142
142
  schemathesis/specs/openapi/utils.py,sha256=XkOJT8qD-6uhq-Tmwxk_xYku1Gy5F9pKL3ldNg_DRZw,522
@@ -178,8 +178,8 @@ schemathesis/transport/prepare.py,sha256=erYXRaxpQokIDzaIuvt_csHcw72iHfCyNq8VNEz
178
178
  schemathesis/transport/requests.py,sha256=jJAKqmsnlErU066WlUTIZHa3rx5lFtHWT43nm8CewUc,10717
179
179
  schemathesis/transport/serialization.py,sha256=GwO6OAVTmL1JyKw7HiZ256tjV4CbrRbhQN0ep1uaZwI,11157
180
180
  schemathesis/transport/wsgi.py,sha256=kQtasFre6pjdJWRKwLA_Qb-RyQHCFNpaey9ubzlFWKI,5907
181
- schemathesis-4.3.15.dist-info/METADATA,sha256=4huPYaRVN0uReWxrIBrV0LSn1mLHu91RxF-O-J9KIIo,8566
182
- schemathesis-4.3.15.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
183
- schemathesis-4.3.15.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
184
- schemathesis-4.3.15.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
185
- schemathesis-4.3.15.dist-info/RECORD,,
181
+ schemathesis-4.3.16.dist-info/METADATA,sha256=C--ciooZRdh7oAzk5hwFfccPqx1-G8QPHTTxbh_lZGw,8566
182
+ schemathesis-4.3.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
183
+ schemathesis-4.3.16.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
184
+ schemathesis-4.3.16.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
185
+ schemathesis-4.3.16.dist-info/RECORD,,