schemathesis 4.0.6__py3-none-any.whl → 4.0.8__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.
@@ -40,7 +40,7 @@ class JunitXMLHandler(EventHandler):
40
40
  test_suites = [
41
41
  TestSuite("schemathesis", test_cases=list(self.test_cases.values()), hostname=platform.node())
42
42
  ]
43
- with open(self.path, "w") as fd:
43
+ with open(self.path, "w", encoding="utf-8") as fd:
44
44
  to_xml_report_file(file_descriptor=fd, test_suites=test_suites, prettyprint=True, encoding="utf-8")
45
45
 
46
46
  def get_or_create_test_case(self, label: str) -> TestCase:
@@ -4,6 +4,9 @@ import re
4
4
  from functools import lru_cache
5
5
  from typing import TYPE_CHECKING, Any, BinaryIO, TextIO
6
6
 
7
+ from schemathesis.core import media_types
8
+ from schemathesis.core.transport import Response
9
+
7
10
  if TYPE_CHECKING:
8
11
  import yaml
9
12
 
@@ -63,3 +66,14 @@ def deserialize_yaml(stream: str | bytes | TextIO | BinaryIO) -> Any:
63
66
  import yaml
64
67
 
65
68
  return yaml.load(stream, get_yaml_loader())
69
+
70
+
71
+ def deserialize_response(response: Response, content_type: str) -> Any:
72
+ if media_types.is_yaml(content_type):
73
+ encoding = response.encoding or "utf-8"
74
+ return deserialize_yaml(response.content.decode(encoding))
75
+ if media_types.is_json(content_type):
76
+ return response.json()
77
+ raise NotImplementedError(
78
+ f"Unsupported Content-Type: {content_type!r}. Supported types are: application/yaml, application/json."
79
+ )
@@ -55,7 +55,7 @@ def is_json(value: str) -> bool:
55
55
 
56
56
  def is_yaml(value: str) -> bool:
57
57
  """Detect whether the content type is YAML-compatible."""
58
- return value in ("text/yaml", "text/x-yaml", "application/x-yaml", "text/vnd.yaml")
58
+ return value in ("text/yaml", "text/x-yaml", "application/x-yaml", "text/vnd.yaml", "application/yaml")
59
59
 
60
60
 
61
61
  def is_plain_text(value: str) -> bool:
@@ -1,7 +1,7 @@
1
1
  from importlib import metadata
2
2
 
3
3
  try:
4
- SCHEMATHESIS_VERSION = metadata.version(__package__)
4
+ SCHEMATHESIS_VERSION = metadata.version("schemathesis")
5
5
  except metadata.PackageNotFoundError:
6
6
  # Local run without installation
7
7
  SCHEMATHESIS_VERSION = "dev"
@@ -14,7 +14,7 @@ from hypothesis_jsonschema import from_schema
14
14
  from requests.structures import CaseInsensitiveDict
15
15
 
16
16
  from schemathesis.config import GenerationConfig
17
- from schemathesis.core import NOT_SET, NotSet, media_types
17
+ from schemathesis.core import NOT_SET, media_types
18
18
  from schemathesis.core.control import SkipTest
19
19
  from schemathesis.core.errors import SERIALIZERS_SUGGESTION_MESSAGE, SerializationNotPossible
20
20
  from schemathesis.core.transforms import deepclone
@@ -57,10 +57,10 @@ def openapi_cases(
57
57
  hooks: HookDispatcher | None = None,
58
58
  auth_storage: auths.AuthStorage | None = None,
59
59
  generation_mode: GenerationMode = GenerationMode.POSITIVE,
60
- path_parameters: NotSet | dict[str, Any] = NOT_SET,
61
- headers: NotSet | dict[str, Any] = NOT_SET,
62
- cookies: NotSet | dict[str, Any] = NOT_SET,
63
- query: NotSet | dict[str, Any] = NOT_SET,
60
+ path_parameters: dict[str, Any] | None = None,
61
+ headers: dict[str, Any] | None = None,
62
+ cookies: dict[str, Any] | None = None,
63
+ query: dict[str, Any] | None = None,
64
64
  body: Any = NOT_SET,
65
65
  media_type: str | None = None,
66
66
  phase: TestPhase = TestPhase.FUZZING,
@@ -220,7 +220,7 @@ def _get_body_strategy(
220
220
 
221
221
 
222
222
  def get_parameters_value(
223
- value: NotSet | dict[str, Any],
223
+ value: dict[str, Any] | None,
224
224
  location: str,
225
225
  draw: Callable,
226
226
  operation: APIOperation,
@@ -234,7 +234,7 @@ def get_parameters_value(
234
234
  If the value is not set, then generate it from the relevant strategy. Otherwise, check what is missing in it and
235
235
  generate those parts.
236
236
  """
237
- if isinstance(value, NotSet) or not value:
237
+ if value is None:
238
238
  strategy = get_parameters_strategy(operation, strategy_factory, location, generation_config)
239
239
  strategy = apply_hooks(operation, ctx, hooks, strategy, location)
240
240
  return draw(strategy)
@@ -274,7 +274,7 @@ def any_negated_values(values: list[ValueContainer]) -> bool:
274
274
 
275
275
  def generate_parameter(
276
276
  location: str,
277
- explicit: NotSet | dict[str, Any],
277
+ explicit: dict[str, Any] | None,
278
278
  operation: APIOperation,
279
279
  draw: Callable,
280
280
  ctx: HookContext,
@@ -223,6 +223,7 @@ def change_type(context: MutationContext, draw: Draw, schema: Schema) -> Mutatio
223
223
  if len(candidates) == 1:
224
224
  new_type = candidates.pop()
225
225
  schema["type"] = new_type
226
+ _ensure_query_serializes_to_non_empty(context, schema)
226
227
  prevent_unsatisfiable_schema(schema, new_type)
227
228
  return MutationResult.SUCCESS
228
229
  # Choose one type that will be present in the final candidates list
@@ -235,10 +236,18 @@ def change_type(context: MutationContext, draw: Draw, schema: Schema) -> Mutatio
235
236
  ]
236
237
  new_type = draw(st.sampled_from(remaining_candidates))
237
238
  schema["type"] = new_type
239
+ _ensure_query_serializes_to_non_empty(context, schema)
238
240
  prevent_unsatisfiable_schema(schema, new_type)
239
241
  return MutationResult.SUCCESS
240
242
 
241
243
 
244
+ def _ensure_query_serializes_to_non_empty(context: MutationContext, schema: Schema) -> None:
245
+ if context.is_query_location and schema.get("type") == "array":
246
+ # Query parameters with empty arrays or arrays of `None` or empty arrays / objects will not appear in the final URL
247
+ schema["minItems"] = schema.get("minItems") or 1
248
+ schema.setdefault("items", {}).update({"not": {"enum": [None, [], {}]}})
249
+
250
+
242
251
  def _get_type_candidates(context: MutationContext, schema: Schema) -> set[str]:
243
252
  types = set(get_type(schema))
244
253
  if context.is_path_location:
@@ -29,7 +29,7 @@ from requests.exceptions import InvalidHeader
29
29
  from requests.structures import CaseInsensitiveDict
30
30
  from requests.utils import check_header_validity
31
31
 
32
- from schemathesis.core import NOT_SET, NotSet, Specification, media_types
32
+ from schemathesis.core import NOT_SET, NotSet, Specification, deserialization, media_types
33
33
  from schemathesis.core.compat import RefResolutionError
34
34
  from schemathesis.core.errors import InternalError, InvalidSchema, LoaderError, LoaderErrorKind, OperationNotFound
35
35
  from schemathesis.core.failures import Failure, FailureGroup, MalformedJson
@@ -617,17 +617,30 @@ class BaseOpenAPISchema(BaseSchema):
617
617
  formatted_content_types = [f"\n- `{content_type}`" for content_type in all_media_types]
618
618
  message = f"The following media types are documented in the schema:{''.join(formatted_content_types)}"
619
619
  failures.append(MissingContentType(operation=operation.label, message=message, media_types=all_media_types))
620
- content_type = None
620
+ # Default content type
621
+ content_type = "application/json"
621
622
  else:
622
623
  content_type = content_types[0]
623
- if content_type and not media_types.is_json(content_type):
624
- _maybe_raise_one_or_more(failures)
625
- return None
626
624
  try:
627
- data = response.json()
625
+ data = deserialization.deserialize_response(response, content_type)
628
626
  except JSONDecodeError as exc:
629
627
  failures.append(MalformedJson.from_exception(operation=operation.label, exc=exc))
630
628
  _maybe_raise_one_or_more(failures)
629
+ return None
630
+ except NotImplementedError:
631
+ # If the content type is not supported, we cannot validate it
632
+ _maybe_raise_one_or_more(failures)
633
+ return None
634
+ except Exception as exc:
635
+ failures.append(
636
+ Failure(
637
+ operation=operation.label,
638
+ title="Content deserialization error",
639
+ message=f"Failed to deserialize response content:\n\n {exc}",
640
+ )
641
+ )
642
+ _maybe_raise_one_or_more(failures)
643
+ return None
631
644
  with self._validating_response(scopes) as resolver:
632
645
  try:
633
646
  jsonschema.validate(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 4.0.6
3
+ Version: 4.0.8
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
@@ -22,7 +22,7 @@ schemathesis/cli/commands/run/validation.py,sha256=FzCzYdW1-hn3OgyzPO1p6wHEX5PG7
22
22
  schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31MAXXn1qI7uU4FtiDwroXZI,1915
23
23
  schemathesis/cli/commands/run/handlers/base.py,sha256=yDsTtCiztLksfk7cRzg8JlaAVOfS-zwK3tsJMOXAFyc,530
24
24
  schemathesis/cli/commands/run/handlers/cassettes.py,sha256=rRD4byjp4HXCkJS-zx3jSIFOJsPq77ejPpYeyCtsEZs,19461
25
- schemathesis/cli/commands/run/handlers/junitxml.py,sha256=3KylA3wDBzpGjUhQnNIT6rfCo_8uh29epcUOY5bHHzQ,2568
25
+ schemathesis/cli/commands/run/handlers/junitxml.py,sha256=ydk6Ofj-Uti6H8EucT4Snp85cmTA5W7uVpKkoHrIDKE,2586
26
26
  schemathesis/cli/commands/run/handlers/output.py,sha256=IRERu8Mir3yi5BgDf40SFwYQbxd0aTrWE9IRkjdO7h4,62808
27
27
  schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
28
  schemathesis/cli/ext/fs.py,sha256=3lvoAsEDDdih75ITJJNxemd3nwxX55gGWrI7uDxm0cM,447
@@ -50,7 +50,7 @@ schemathesis/core/__init__.py,sha256=j862XBH5dXhxsrDg9mE7n4cSSfol0EHdY0ru1d27tCc
50
50
  schemathesis/core/compat.py,sha256=9BWCrFoqN2sJIaiht_anxe8kLjYMR7t0iiOkXqLRUZ8,1058
51
51
  schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
52
52
  schemathesis/core/curl.py,sha256=yuaCe_zHLGwUjEeloQi6W3tOA3cGdnHDNI17-5jia0o,1723
53
- schemathesis/core/deserialization.py,sha256=ygIj4fNaOd0mJ2IvTsn6bsabBt_2AbSLCz-z9UqfpdQ,2406
53
+ schemathesis/core/deserialization.py,sha256=qjXUPaz_mc1OSgXzTUSkC8tuVR8wgVQtb9g3CcAF6D0,2951
54
54
  schemathesis/core/errors.py,sha256=KuFLy5ZOGn8KlD4ai5HK_WFlB3fJ2rSKwV1yD4fn4BU,16295
55
55
  schemathesis/core/failures.py,sha256=MYyRnom-XeUEuBmq2ffdz34xhxmpSHWaQunfHtliVsY,8932
56
56
  schemathesis/core/fs.py,sha256=ItQT0_cVwjDdJX9IiI7EnU75NI2H3_DCEyyUjzg_BgI,472
@@ -58,14 +58,14 @@ schemathesis/core/hooks.py,sha256=qhbkkRSf8URJ4LKv2wmKRINKpquUOgxQzWBHKWRWo3Q,47
58
58
  schemathesis/core/lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
59
59
  schemathesis/core/loaders.py,sha256=SQQ-8m64-D2FaOgvwKZLyTtLJuzP3RPo7Ud2BERK1c0,3404
60
60
  schemathesis/core/marks.py,sha256=SH7jsVuNRJjx2gZN9Ze5MY01u7FJiHeO0iruzKi5rm4,2135
61
- schemathesis/core/media_types.py,sha256=vV0CEu34sDoZWXvu4R1Y1HosxVS4YXZV8iTov8fU3X0,2148
61
+ schemathesis/core/media_types.py,sha256=ThdAikBttdRD1RB9-83rMmtG_z-BdW8xidUxzhgdUqI,2168
62
62
  schemathesis/core/rate_limit.py,sha256=7tg9Znk11erTfw8-ANutjEmu7hbfUHZx_iEdkoaP174,1757
63
63
  schemathesis/core/registries.py,sha256=T4jZB4y3zBHdeSgQc0pRbgSeMblvO-6z4I3zmzIfTi0,811
64
64
  schemathesis/core/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo2Q,461
65
65
  schemathesis/core/transforms.py,sha256=63aeLkR93r3krq4CwYtDcoS_pFBky4L16c9DcFsBHuE,3535
66
66
  schemathesis/core/transport.py,sha256=LQcamAkFqJ0HuXQzepevAq2MCJW-uq5Nm-HE9yc7HMI,7503
67
67
  schemathesis/core/validation.py,sha256=rnhzsqWukMWyrc7sRm0kZNHTePoPCQ3A4kLpLxrb0jM,1641
68
- schemathesis/core/version.py,sha256=O-6yFbNocbD4RDwiBZLborxTp54htyKxBWTqpZDnPvg,202
68
+ schemathesis/core/version.py,sha256=dOBUWrY3-uA2NQXJp9z7EtZgkR6jYeLg8sMhQCL1mcI,205
69
69
  schemathesis/core/output/__init__.py,sha256=SiHqONFskXl73AtP5dV29L14nZoKo7B-IeG52KZB32M,1446
70
70
  schemathesis/core/output/sanitization.py,sha256=Ev3tae8dVwsYd7yVb2_1VBFYs92WFsQ4Eu1fGaymItE,2013
71
71
  schemathesis/engine/__init__.py,sha256=QaFE-FinaTAaarteADo2RRMJ-Sz6hZB9TzD5KjMinIA,706
@@ -123,7 +123,7 @@ 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=usufzl_VyBLgI6riTZ-pGqKnSLndw89GRIuCgCH9QiY,22366
126
+ schemathesis/specs/openapi/_hypothesis.py,sha256=H-4pzT7dECY-AcDGhebKdTSELhGOdyA1WCbZQSMZY3E,22309
127
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
@@ -134,7 +134,7 @@ schemathesis/specs/openapi/media_types.py,sha256=F5M6TKl0s6Z5X8mZpPsWDEdPBvxclKR
134
134
  schemathesis/specs/openapi/parameters.py,sha256=BevME4DWLQ-OFvc_7fREMjj99VAbVNxVb5i8OEX6Pfs,14453
135
135
  schemathesis/specs/openapi/patterns.py,sha256=cBj8W4wn7VLJd4nABaIH5f502-zBDiqljxLgPWUn-50,15609
136
136
  schemathesis/specs/openapi/references.py,sha256=40YcDExPLR2B8EOwt-Csw-5MYFi2xj_DXf91J0Pc9d4,8855
137
- schemathesis/specs/openapi/schemas.py,sha256=TIKj9dnCHkBdBofgt0UH4fMByklv5ijy-JAc8i9_D5Y,51415
137
+ schemathesis/specs/openapi/schemas.py,sha256=4bL-nOFOykfOGHbkmUor9MnCqhAzW5S5oIQbL36wvVc,51972
138
138
  schemathesis/specs/openapi/security.py,sha256=6UWYMhL-dPtkTineqqBFNKca1i4EuoTduw-EOLeE0aQ,7149
139
139
  schemathesis/specs/openapi/serialization.py,sha256=VdDLmeHqxlWM4cxQQcCkvrU6XurivolwEEaT13ohelA,11972
140
140
  schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
@@ -145,7 +145,7 @@ schemathesis/specs/openapi/expressions/lexer.py,sha256=ZbYPbVX-2c2Dan-6fi4NrDlFT
145
145
  schemathesis/specs/openapi/expressions/nodes.py,sha256=qaFpAM3seIzmlYLr9So2kRCSNrteZTa7djcRiOD_ji4,4811
146
146
  schemathesis/specs/openapi/expressions/parser.py,sha256=e-ZxshrGE_5CVbgcZLYgdGSjdifgyzgKkLQp0dI0cJY,4503
147
147
  schemathesis/specs/openapi/negative/__init__.py,sha256=1sajF22SrE4pUK7-C6PiseZ9PiR5trN33cfUqEMGIbo,3915
148
- schemathesis/specs/openapi/negative/mutations.py,sha256=ZLiNb4n2K1Oeq3eev8tgNqvlyP7cBIPUTLe7Gc6nvDM,19318
148
+ schemathesis/specs/openapi/negative/mutations.py,sha256=xDSUVnGWjuuIcvmW_mJGChf-G-nXst-JBX1okQAzon4,19865
149
149
  schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
150
150
  schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
151
151
  schemathesis/specs/openapi/stateful/__init__.py,sha256=FSitLbJxjBYU814cqjI_QCkdyoIc-9xfT_1HQcYwsXw,15064
@@ -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.6.dist-info/METADATA,sha256=qhqeHLsnB9ltBzTHOeqyd1aZElO-XGs2HTMs_2PYA6k,8471
161
- schemathesis-4.0.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
- schemathesis-4.0.6.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
- schemathesis-4.0.6.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
- schemathesis-4.0.6.dist-info/RECORD,,
160
+ schemathesis-4.0.8.dist-info/METADATA,sha256=drG7CbWUVvqW5yB1wAuNZeU4M8Meq1Bcd0aMag2EZmA,8471
161
+ schemathesis-4.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
162
+ schemathesis-4.0.8.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
163
+ schemathesis-4.0.8.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
164
+ schemathesis-4.0.8.dist-info/RECORD,,