schemathesis 3.38.10__py3-none-any.whl → 3.39.0__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/_xml.py CHANGED
@@ -2,9 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import re
5
6
  from io import StringIO
6
7
  from typing import Any, Dict, List, Union
7
- from xml.etree import ElementTree
8
+ from unicodedata import normalize
8
9
 
9
10
  from .exceptions import UnboundPrefixError
10
11
  from .internal.copy import fast_deepcopy
@@ -32,24 +33,9 @@ def _to_xml(value: Any, raw_schema: dict[str, Any] | None, resolved_schema: dict
32
33
  namespace_stack: list[str] = []
33
34
  _write_xml(buffer, value, tag, resolved_schema, namespace_stack)
34
35
  data = buffer.getvalue()
35
- if not is_valid_xml(data):
36
- from hypothesis import reject
37
-
38
- reject()
39
36
  return {"data": data.encode("utf8")}
40
37
 
41
38
 
42
- _from_string = ElementTree.fromstring
43
-
44
-
45
- def is_valid_xml(data: str) -> bool:
46
- try:
47
- _from_string(f"<root xmlns:smp='{NAMESPACE_URL}'>{data}</root>")
48
- return True
49
- except ElementTree.ParseError:
50
- return False
51
-
52
-
53
39
  def _get_xml_tag(raw_schema: dict[str, Any] | None, resolved_schema: dict[str, Any] | None) -> str:
54
40
  # On the top level we need to detect the proper XML tag, in other cases it is known from object properties
55
41
  if (resolved_schema or {}).get("xml", {}).get("name"):
@@ -98,11 +84,14 @@ def _write_object(
98
84
  ) -> None:
99
85
  options = (schema or {}).get("xml", {})
100
86
  push_namespace_if_any(stack, options)
87
+ tag = _sanitize_xml_name(tag)
101
88
  if "prefix" in options:
102
89
  tag = f"{options['prefix']}:{tag}"
103
90
  buffer.write(f"<{tag}")
104
91
  if "namespace" in options:
105
92
  _write_namespace(buffer, options)
93
+
94
+ attribute_namespaces = {}
106
95
  attributes = []
107
96
  children_buffer = StringIO()
108
97
  properties = (schema or {}).get("properties", {})
@@ -111,16 +100,31 @@ def _write_object(
111
100
  child_options = property_schema.get("xml", {})
112
101
  push_namespace_if_any(stack, child_options)
113
102
  child_tag = child_options.get("name", child_name)
103
+
104
+ if child_options.get("attribute", False):
105
+ if child_options.get("prefix") and child_options.get("namespace"):
106
+ _validate_prefix(child_options, stack)
107
+ prefix = child_options["prefix"]
108
+ attr_name = f"{prefix}:{_sanitize_xml_name(child_tag)}"
109
+ # Store namespace declaration
110
+ attribute_namespaces[prefix] = child_options["namespace"]
111
+ else:
112
+ attr_name = _sanitize_xml_name(child_tag)
113
+ attributes.append(f'{attr_name}="{_escape_xml(value)}"')
114
+ continue
115
+
116
+ child_tag = _sanitize_xml_name(child_tag)
114
117
  if child_options.get("prefix"):
115
118
  _validate_prefix(child_options, stack)
116
119
  prefix = child_options["prefix"]
117
120
  child_tag = f"{prefix}:{child_tag}"
118
- if child_options.get("attribute", False):
119
- attributes.append(f'{child_tag}="{value}"')
120
- continue
121
121
  _write_xml(children_buffer, value, child_tag, property_schema, stack)
122
122
  pop_namespace_if_any(stack, child_options)
123
123
 
124
+ # Write namespace declarations for attributes
125
+ for prefix, namespace in attribute_namespaces.items():
126
+ buffer.write(f' xmlns:{prefix}="{namespace}"')
127
+
124
128
  if attributes:
125
129
  buffer.write(f" {' '.join(attributes)}")
126
130
  buffer.write(">")
@@ -169,7 +173,7 @@ def _write_primitive(
169
173
  buffer.write(f"<{tag}")
170
174
  if "namespace" in xml_options:
171
175
  _write_namespace(buffer, xml_options)
172
- buffer.write(f">{obj}</{tag}>")
176
+ buffer.write(f">{_escape_xml(obj)}</{tag}>")
173
177
 
174
178
 
175
179
  def _write_namespace(buffer: StringIO, options: dict[str, Any]) -> None:
@@ -182,3 +186,48 @@ def _write_namespace(buffer: StringIO, options: dict[str, Any]) -> None:
182
186
  def _get_tag_name_from_reference(reference: str) -> str:
183
187
  """Extract object name from a reference."""
184
188
  return reference.rsplit("/", maxsplit=1)[1]
189
+
190
+
191
+ def _escape_xml(value: JSON) -> str:
192
+ """Escape special characters in XML content."""
193
+ if isinstance(value, (int, float, bool)):
194
+ return str(value)
195
+ if value is None:
196
+ return ""
197
+
198
+ # Filter out invalid XML characters
199
+ cleaned = "".join(
200
+ char
201
+ for char in str(value)
202
+ if (
203
+ char in "\t\n\r"
204
+ or 0x20 <= ord(char) <= 0xD7FF
205
+ or 0xE000 <= ord(char) <= 0xFFFD
206
+ or 0x10000 <= ord(char) <= 0x10FFFF
207
+ )
208
+ )
209
+
210
+ replacements = {
211
+ "&": "&amp;",
212
+ "<": "&lt;",
213
+ ">": "&gt;",
214
+ '"': "&quot;",
215
+ "'": "&apos;",
216
+ }
217
+ return "".join(replacements.get(c, c) for c in cleaned)
218
+
219
+
220
+ def _sanitize_xml_name(name: str) -> str:
221
+ """Sanitize a string to be a valid XML element name."""
222
+ if not name:
223
+ return "element"
224
+
225
+ name = normalize("NFKC", str(name))
226
+
227
+ name = name.replace(":", "_")
228
+ sanitized = re.sub(r"[^a-zA-Z0-9_\-.]", "_", name)
229
+
230
+ if not sanitized[0].isalpha() and sanitized[0] != "_":
231
+ sanitized = "x_" + sanitized
232
+
233
+ return sanitized
@@ -323,6 +323,15 @@ REPORT_TO_SERVICE = ReportToService()
323
323
  multiple=True,
324
324
  metavar="",
325
325
  )
326
+ @grouped_option(
327
+ "--experimental-no-failfast",
328
+ "no_failfast",
329
+ help="Continue testing an API operation after a failure is found",
330
+ is_flag=True,
331
+ default=False,
332
+ metavar="",
333
+ envvar="SCHEMATHESIS_EXPERIMENTAL_NO_FAILFAST",
334
+ )
326
335
  @grouped_option(
327
336
  "--experimental-missing-required-header-allowed-statuses",
328
337
  "missing_required_header_allowed_statuses",
@@ -864,6 +873,7 @@ def run(
864
873
  set_cookie: dict[str, str],
865
874
  set_path: dict[str, str],
866
875
  experiments: list,
876
+ no_failfast: bool,
867
877
  missing_required_header_allowed_statuses: list[str],
868
878
  positive_data_acceptance_allowed_statuses: list[str],
869
879
  negative_data_rejection_allowed_statuses: list[str],
@@ -1232,6 +1242,7 @@ def run(
1232
1242
  request_timeout=request_timeout,
1233
1243
  seed=hypothesis_seed,
1234
1244
  exit_first=exit_first,
1245
+ no_failfast=no_failfast,
1235
1246
  max_failures=max_failures,
1236
1247
  unique_data=contrib_unique_data,
1237
1248
  dry_run=dry_run,
@@ -1358,6 +1369,7 @@ def into_event_stream(
1358
1369
  output_config: OutputConfig,
1359
1370
  seed: int | None,
1360
1371
  exit_first: bool,
1372
+ no_failfast: bool,
1361
1373
  max_failures: int | None,
1362
1374
  rate_limit: str | None,
1363
1375
  unique_data: bool,
@@ -1402,6 +1414,7 @@ def into_event_stream(
1402
1414
  request_cert=request_cert,
1403
1415
  seed=seed,
1404
1416
  exit_first=exit_first,
1417
+ no_failfast=no_failfast,
1405
1418
  max_failures=max_failures,
1406
1419
  started_at=started_at,
1407
1420
  unique_data=unique_data,
@@ -346,6 +346,7 @@ def from_schema(
346
346
  request_cert: RequestCert | None = None,
347
347
  seed: int | None = None,
348
348
  exit_first: bool = False,
349
+ no_failfast: bool = False,
349
350
  max_failures: int | None = None,
350
351
  started_at: str | None = None,
351
352
  unique_data: bool = False,
@@ -406,6 +407,7 @@ def from_schema(
406
407
  workers_num=workers_num,
407
408
  request_config=request_config,
408
409
  exit_first=exit_first,
410
+ no_failfast=no_failfast,
409
411
  max_failures=max_failures,
410
412
  started_at=started_at,
411
413
  unique_data=unique_data,
@@ -433,6 +435,7 @@ def from_schema(
433
435
  headers=headers,
434
436
  seed=seed,
435
437
  exit_first=exit_first,
438
+ no_failfast=no_failfast,
436
439
  max_failures=max_failures,
437
440
  started_at=started_at,
438
441
  unique_data=unique_data,
@@ -460,6 +463,7 @@ def from_schema(
460
463
  seed=seed,
461
464
  workers_num=workers_num,
462
465
  exit_first=exit_first,
466
+ no_failfast=no_failfast,
463
467
  max_failures=max_failures,
464
468
  started_at=started_at,
465
469
  unique_data=unique_data,
@@ -488,6 +492,7 @@ def from_schema(
488
492
  seed=seed,
489
493
  request_config=request_config,
490
494
  exit_first=exit_first,
495
+ no_failfast=no_failfast,
491
496
  max_failures=max_failures,
492
497
  started_at=started_at,
493
498
  unique_data=unique_data,
@@ -515,6 +520,7 @@ def from_schema(
515
520
  headers=headers,
516
521
  seed=seed,
517
522
  exit_first=exit_first,
523
+ no_failfast=no_failfast,
518
524
  max_failures=max_failures,
519
525
  started_at=started_at,
520
526
  unique_data=unique_data,
@@ -541,6 +547,7 @@ def from_schema(
541
547
  headers=headers,
542
548
  seed=seed,
543
549
  exit_first=exit_first,
550
+ no_failfast=no_failfast,
544
551
  max_failures=max_failures,
545
552
  started_at=started_at,
546
553
  unique_data=unique_data,
@@ -28,8 +28,19 @@ class RunnerContext:
28
28
  outcome_cache: dict[int, BaseException | None]
29
29
  checks_config: CheckConfig
30
30
  override: CaseOverride | None
31
-
32
- __slots__ = ("data", "auth", "seed", "stop_event", "unique_data", "outcome_cache", "checks_config", "override")
31
+ no_failfast: bool
32
+
33
+ __slots__ = (
34
+ "data",
35
+ "auth",
36
+ "seed",
37
+ "stop_event",
38
+ "unique_data",
39
+ "outcome_cache",
40
+ "checks_config",
41
+ "override",
42
+ "no_failfast",
43
+ )
33
44
 
34
45
  def __init__(
35
46
  self,
@@ -40,6 +51,7 @@ class RunnerContext:
40
51
  unique_data: bool,
41
52
  checks_config: CheckConfig,
42
53
  override: CaseOverride | None,
54
+ no_failfast: bool,
43
55
  ) -> None:
44
56
  self.data = TestResultSet(seed=seed)
45
57
  self.auth = auth
@@ -49,6 +61,7 @@ class RunnerContext:
49
61
  self.unique_data = unique_data
50
62
  self.checks_config = checks_config
51
63
  self.override = override
64
+ self.no_failfast = no_failfast
52
65
 
53
66
  def _repr_pretty_(self, *args: Any, **kwargs: Any) -> None: ...
54
67
 
@@ -111,6 +111,7 @@ class BaseRunner:
111
111
  store_interactions: bool = False
112
112
  seed: int | None = None
113
113
  exit_first: bool = False
114
+ no_failfast: bool = False
114
115
  max_failures: int | None = None
115
116
  started_at: str = field(default_factory=current_datetime)
116
117
  unique_data: bool = False
@@ -139,6 +140,7 @@ class BaseRunner:
139
140
  unique_data=self.unique_data,
140
141
  checks_config=self.checks_config,
141
142
  override=self.override,
143
+ no_failfast=self.no_failfast,
142
144
  )
143
145
  start_time = time.monotonic()
144
146
  initialized = None
@@ -672,6 +674,8 @@ def run_test(
672
674
  )
673
675
  else:
674
676
  result.add_error(error)
677
+ if status == Status.success and ctx.no_failfast and any(check.value == Status.failure for check in result.checks):
678
+ status = Status.failure
675
679
  if has_unsatisfied_example_mark(test):
676
680
  status = Status.error
677
681
  result.add_error(
@@ -811,6 +815,7 @@ def run_checks(
811
815
  response: GenericResponse,
812
816
  elapsed_time: float,
813
817
  max_response_time: int | None = None,
818
+ no_failfast: bool,
814
819
  ) -> None:
815
820
  errors = []
816
821
 
@@ -852,7 +857,7 @@ def run_checks(
852
857
  else:
853
858
  result.add_success("max_response_time", case, response, elapsed_time)
854
859
 
855
- if errors:
860
+ if errors and not no_failfast:
856
861
  raise get_grouped_exception(case.operation.verbose_name, *errors)(causes=tuple(errors))
857
862
 
858
863
 
@@ -1042,6 +1047,7 @@ def _network_test(
1042
1047
  response=response,
1043
1048
  elapsed_time=context.response_time * 1000,
1044
1049
  max_response_time=max_response_time,
1050
+ no_failfast=ctx.no_failfast,
1045
1051
  )
1046
1052
  except CheckFailed:
1047
1053
  status = Status.failure
@@ -1140,6 +1146,7 @@ def _wsgi_test(
1140
1146
  response=response,
1141
1147
  elapsed_time=context.response_time * 1000,
1142
1148
  max_response_time=max_response_time,
1149
+ no_failfast=ctx.no_failfast,
1143
1150
  )
1144
1151
  except CheckFailed:
1145
1152
  status = Status.failure
@@ -1226,6 +1233,7 @@ def _asgi_test(
1226
1233
  response=response,
1227
1234
  elapsed_time=context.response_time * 1000,
1228
1235
  max_response_time=max_response_time,
1236
+ no_failfast=ctx.no_failfast,
1229
1237
  )
1230
1238
  except CheckFailed:
1231
1239
  status = Status.failure
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 3.38.10
3
+ Version: 3.39.0
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
@@ -9,7 +9,8 @@ Project-URL: Funding, https://github.com/sponsors/Stranger6667
9
9
  Project-URL: Source Code, https://github.com/schemathesis/schemathesis
10
10
  Author-email: Dmitry Dygalo <dmitry@dygalo.dev>
11
11
  Maintainer-email: Dmitry Dygalo <dmitry@dygalo.dev>
12
- License: MIT
12
+ License-Expression: MIT
13
+ License-File: LICENSE
13
14
  Keywords: graphql,hypothesis,openapi,pytest,testing
14
15
  Classifier: Development Status :: 5 - Production/Stable
15
16
  Classifier: Environment :: Console
@@ -6,7 +6,7 @@ schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,
6
6
  schemathesis/_override.py,sha256=TAjYB3eJQmlw9K_xiR9ptt9Wj7if4U7UFlUhGjpBAoM,1625
7
7
  schemathesis/_patches.py,sha256=Hsbpn4UVeXUQD2Kllrbq01CSWsTYENWa0VJTyhX5C2k,895
8
8
  schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
9
- schemathesis/_xml.py,sha256=lwRaKEMQvl6SUySnhGgooNsdmXPnDh11YSPpbsW8dR8,6923
9
+ schemathesis/_xml.py,sha256=_R8h8dn2VepX8EywGnQZOjLw8qg5uIjHEHll4G_BkN8,8467
10
10
  schemathesis/auths.py,sha256=De97IS_iOlC36-jRhkZ2DUndjUpXYgsd8R-nA-iHn88,16837
11
11
  schemathesis/checks.py,sha256=YPUI1N5giGBy1072vd77e6HWelGAKrJUmJLEG4oqfF8,2630
12
12
  schemathesis/code_samples.py,sha256=rsdTo6ksyUs3ZMhqx0mmmkPSKUCFa--snIOYsXgZd80,4120
@@ -28,7 +28,7 @@ schemathesis/targets.py,sha256=XIGRghvEzbmEJjse9aZgNEj67L3jAbiazm2rxURWgDE,2351
28
28
  schemathesis/throttling.py,sha256=aisUc4MJDGIOGUAs9L2DlWWpdd4KyAFuNVKhYoaUC9M,1719
29
29
  schemathesis/types.py,sha256=Tem2Q_zyMCd0Clp5iGKv6Fu13wdcbxVE8tCVH9WWt7A,1065
30
30
  schemathesis/utils.py,sha256=LwqxqoAKmRiAdj-qUbNmgQgsamc49V5lc5TnOIDuuMA,4904
31
- schemathesis/cli/__init__.py,sha256=GlM52jH2p8pSoHnSc0mfxNRTGVLslT_9rTLLy3jgbgo,75272
31
+ schemathesis/cli/__init__.py,sha256=rPhFfXv1_RTwJZTpCYLloQ7H-TZCgH4D8-jb8nQ-N_0,75636
32
32
  schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
33
33
  schemathesis/cli/callbacks.py,sha256=-VA_I_mVma9WxFNtUR8d2KNICKJD5ScayfSdKKPEP5Y,16321
34
34
  schemathesis/cli/cassettes.py,sha256=zji-B-uuwyr0Z0BzQX-DLMV6lWb58JtLExcUE1v3m4Y,20153
@@ -74,13 +74,13 @@ schemathesis/internal/output.py,sha256=zMaG5knIuBieVH8CrcmPJgbmQukDs2xdekX0BrK7B
74
74
  schemathesis/internal/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo2Q,461
75
75
  schemathesis/internal/transformation.py,sha256=M5LA4pFZC4nD_0iGfih1wLF3_q8xJas94Uuaymt-7Cw,690
76
76
  schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QAtMPyEw,966
77
- schemathesis/runner/__init__.py,sha256=b96aoJQo9Kash0GNKI-uCiLMEKOI8cxKjKCKQlWxkUw,21925
77
+ schemathesis/runner/__init__.py,sha256=r8SoHc3X_wk5lfmt9P87gzv6nvbIowkQ2-WlT7fhReY,22182
78
78
  schemathesis/runner/events.py,sha256=cRKKSDvHvKLBIyFBz-J0JtAKshbGGKco9eaMyLCgzsY,11734
79
79
  schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
80
80
  schemathesis/runner/serialization.py,sha256=vZi1wd9HX9Swp9VJ_hZFeDgy3Y726URpHra-TbPvQhk,20762
81
81
  schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
82
- schemathesis/runner/impl/context.py,sha256=8pedlvOnh7pjvEMveaSWCUiG4s12ST4EZCJqcWLu5Bg,2950
83
- schemathesis/runner/impl/core.py,sha256=GfE_5_Ye9OwuayXIPu5kziTt90N6Zhvkus-pSl1mV9o,47697
82
+ schemathesis/runner/impl/context.py,sha256=oEdkXnlibVDobDRCMliImQwtX5RPEKgVEwVBCN67mfE,3132
83
+ schemathesis/runner/impl/core.py,sha256=aUeJxW3cvfi5IYwU2GqhDfcKrCK3GtMnsts-qyaIXGQ,48086
84
84
  schemathesis/runner/impl/solo.py,sha256=y5QSxgK8nBCEjZVD5BpFvYUXmB6tEjk6TwxAo__NejA,2911
85
85
  schemathesis/runner/impl/threadpool.py,sha256=yNR5LYE8f3N_4t42OwSgy0_qdGgBPM7d11F9c9oEAAs,15075
86
86
  schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
@@ -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.38.10.dist-info/METADATA,sha256=_4IfLofMFUrs7rZwAEyZzOdjcafLgb4OiivgOJzIzko,12924
157
- schemathesis-3.38.10.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
158
- schemathesis-3.38.10.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
159
- schemathesis-3.38.10.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
160
- schemathesis-3.38.10.dist-info/RECORD,,
156
+ schemathesis-3.39.0.dist-info/METADATA,sha256=ZFA8TF6uMkvkRtwPmSkjI0UrgLVOsuZHLqdikcGw6gk,12956
157
+ schemathesis-3.39.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
158
+ schemathesis-3.39.0.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
159
+ schemathesis-3.39.0.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
160
+ schemathesis-3.39.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.3
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any