schemathesis 3.39.0__py3-none-any.whl → 3.39.2__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
@@ -92,7 +92,7 @@ def _write_object(
92
92
  _write_namespace(buffer, options)
93
93
 
94
94
  attribute_namespaces = {}
95
- attributes = []
95
+ attributes = {}
96
96
  children_buffer = StringIO()
97
97
  properties = (schema or {}).get("properties", {})
98
98
  for child_name, value in obj.items():
@@ -110,7 +110,9 @@ def _write_object(
110
110
  attribute_namespaces[prefix] = child_options["namespace"]
111
111
  else:
112
112
  attr_name = _sanitize_xml_name(child_tag)
113
- attributes.append(f'{attr_name}="{_escape_xml(value)}"')
113
+
114
+ if attr_name not in attributes: # Only keep first occurrence
115
+ attributes[attr_name] = f'{attr_name}="{_escape_xml(value)}"'
114
116
  continue
115
117
 
116
118
  child_tag = _sanitize_xml_name(child_tag)
@@ -126,7 +128,7 @@ def _write_object(
126
128
  buffer.write(f' xmlns:{prefix}="{namespace}"')
127
129
 
128
130
  if attributes:
129
- buffer.write(f" {' '.join(attributes)}")
131
+ buffer.write(f" {' '.join(attributes.values())}")
130
132
  buffer.write(">")
131
133
  buffer.write(children_buffer.getvalue())
132
134
  buffer.write(f"</{tag}>")
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
24
24
  @dataclass
25
25
  class JunitXMLHandler(EventHandler):
26
26
  file_handle: LazyFile
27
- test_cases: list = field(default_factory=list)
27
+ test_cases: dict = field(default_factory=dict)
28
28
 
29
29
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
30
30
  if isinstance(event, (events.AfterExecution, events.AfterStatefulExecution)):
@@ -33,23 +33,31 @@ class JunitXMLHandler(EventHandler):
33
33
  name = f"{event_.result.method} {event_.result.path}"
34
34
  else:
35
35
  name = event_.result.verbose_name
36
- test_case = TestCase(name, elapsed_sec=event_.elapsed_time, allow_multiple_subelements=True)
36
+ if name in self.test_cases:
37
+ test_case = self.test_cases[name]
38
+ test_case.elapsed_sec += event_.elapsed_time
39
+ else:
40
+ test_case = TestCase(name, elapsed_sec=event_.elapsed_time, allow_multiple_subelements=True)
37
41
  if event_.status == Status.failure:
38
42
  _add_failure(test_case, event_.result.checks, context)
39
43
  elif event_.status == Status.error:
40
- test_case.add_error_info(message=build_error_message(context, event_.result.errors[-1]))
44
+ test_case.add_error_info(output=build_error_message(context, event_.result.errors[-1]))
41
45
  elif event_.status == Status.skip:
42
- test_case.add_skipped_info(message=event_.result.skip_reason)
43
- self.test_cases.append(test_case)
46
+ test_case.add_skipped_info(output=event_.result.skip_reason)
47
+ self.test_cases[name] = test_case
44
48
  elif isinstance(event, events.Finished):
45
- test_suites = [TestSuite("schemathesis", test_cases=self.test_cases, hostname=platform.node())]
49
+ test_suites = [
50
+ TestSuite("schemathesis", test_cases=list(self.test_cases.values()), hostname=platform.node())
51
+ ]
46
52
  to_xml_report_file(file_descriptor=self.file_handle, test_suites=test_suites, prettyprint=True)
47
53
 
48
54
 
49
55
  def _add_failure(test_case: TestCase, checks: list[SerializedCheck], context: ExecutionContext) -> None:
56
+ messages = []
50
57
  for idx, (code_sample, group) in enumerate(group_by_case(checks, context.code_sample_style), 1):
51
58
  checks = sorted(group, key=lambda c: c.name != "not_a_server_error")
52
- test_case.add_failure_info(message=build_failure_message(context, idx, code_sample, checks))
59
+ messages.append(build_failure_message(context, idx, code_sample, checks))
60
+ test_case.add_failure_info(message="\n\n".join(messages))
53
61
 
54
62
 
55
63
  def build_failure_message(context: ExecutionContext, idx: int, code_sample: str, checks: list[SerializedCheck]) -> str:
@@ -317,7 +317,7 @@ def use_after_free(ctx: CheckContext, response: GenericResponse, original: Case)
317
317
 
318
318
  if not isinstance(original.operation.schema, BaseOpenAPISchema):
319
319
  return True
320
- if response.status_code == 404 or not original.source:
320
+ if response.status_code == 404 or not original.source or response.status_code >= 500:
321
321
  return None
322
322
  response = original.source.response
323
323
  case = original.source.case
@@ -366,6 +366,10 @@ def ensure_resource_availability(ctx: CheckContext, response: GenericResponse, o
366
366
  and original.source.case.operation.method.upper() == "POST"
367
367
  and 200 <= original.source.response.status_code < 400
368
368
  and original.source.overrides_all_parameters
369
+ and _is_prefix_operation(
370
+ ResourcePath(original.source.case.path, original.source.case.path_parameters or {}),
371
+ ResourcePath(original.path, original.path_parameters or {}),
372
+ )
369
373
  ):
370
374
  created_with = original.source.case.operation.verbose_name
371
375
  not_available_with = original.operation.verbose_name
@@ -160,6 +160,9 @@ class BaseOpenAPISchema(BaseSchema):
160
160
  ) -> bool:
161
161
  if method not in HTTP_METHODS:
162
162
  return True
163
+ if self.filter_set.is_empty():
164
+ return False
165
+ path = self.get_full_path(path)
163
166
  # Attribute assignment is way faster than creating a new namespace every time
164
167
  operation = _ctx_cache.operation
165
168
  operation.method = method
@@ -175,17 +178,15 @@ class BaseOpenAPISchema(BaseSchema):
175
178
  paths = self.raw_schema["paths"]
176
179
  except KeyError:
177
180
  return
178
- get_full_path = self.get_full_path
179
181
  resolve = self.resolver.resolve
180
182
  should_skip = self._should_skip
181
183
  for path, path_item in paths.items():
182
- full_path = get_full_path(path)
183
184
  try:
184
185
  if "$ref" in path_item:
185
186
  _, path_item = resolve(path_item["$ref"])
186
187
  # Straightforward iteration is faster than converting to a set & calculating length.
187
188
  for method, definition in path_item.items():
188
- if should_skip(full_path, method, definition):
189
+ if should_skip(path, method, definition):
189
190
  continue
190
191
  yield definition
191
192
  except SCHEMA_PARSING_ERRORS:
@@ -282,7 +283,6 @@ class BaseOpenAPISchema(BaseSchema):
282
283
 
283
284
  context = HookContext()
284
285
  # Optimization: local variables are faster than attribute access
285
- get_full_path = self.get_full_path
286
286
  dispatch_hook = self.dispatch_hook
287
287
  resolve_path_item = self._resolve_path_item
288
288
  resolve_shared_parameters = self._resolve_shared_parameters
@@ -294,7 +294,6 @@ class BaseOpenAPISchema(BaseSchema):
294
294
  for path, path_item in paths.items():
295
295
  method = None
296
296
  try:
297
- full_path = get_full_path(path) # Should be available for later use
298
297
  dispatch_hook("before_process_path", context, path, path_item)
299
298
  scope, path_item = resolve_path_item(path_item)
300
299
  with in_scope(self.resolver, scope):
@@ -304,7 +303,7 @@ class BaseOpenAPISchema(BaseSchema):
304
303
  continue
305
304
  try:
306
305
  resolved = resolve_operation(entry)
307
- if should_skip(full_path, method, resolved):
306
+ if should_skip(path, method, resolved):
308
307
  continue
309
308
  parameters = resolved.get("parameters", ())
310
309
  parameters = collect_parameters(itertools.chain(parameters, shared_parameters), resolved)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: schemathesis
3
- Version: 3.39.0
3
+ Version: 3.39.2
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
@@ -41,7 +41,7 @@ Requires-Dist: hypothesis[zoneinfo]<7,>=6.103.4; python_version == '3.8'
41
41
  Requires-Dist: jsonschema[format]<5.0,>=4.18.0
42
42
  Requires-Dist: junit-xml<2.0,>=1.9
43
43
  Requires-Dist: pyrate-limiter<4.0,>=2.10
44
- Requires-Dist: pytest-subtests<0.14.0,>=0.2.1
44
+ Requires-Dist: pytest-subtests<0.15.0,>=0.2.1
45
45
  Requires-Dist: pytest<9,>=4.6.4
46
46
  Requires-Dist: pyyaml<7.0,>=5.1
47
47
  Requires-Dist: requests<3,>=2.22
@@ -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=_R8h8dn2VepX8EywGnQZOjLw8qg5uIjHEHll4G_BkN8,8467
9
+ schemathesis/_xml.py,sha256=qc2LydEwIqcSfgqQOJqiYicivA4YFJGKgCBOem_JqNc,8560
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
@@ -36,7 +36,7 @@ schemathesis/cli/constants.py,sha256=wk-0GsoJIel8wFFerQ6Kf_6eAYUtIWkwMFwyAqv3yj4
36
36
  schemathesis/cli/context.py,sha256=j_lvYQiPa6Q7P4P_IGCM9V2y2gJSpDbpxIIzR5oFB2I,2567
37
37
  schemathesis/cli/debug.py,sha256=_YA-bX1ujHl4bqQDEum7M-I2XHBTEGbvgkhvcvKhmgU,658
38
38
  schemathesis/cli/handlers.py,sha256=EXSAFe5TQlHANz1AVlSttfsoDT2oeaeFbqq1N7e2udw,467
39
- schemathesis/cli/junitxml.py,sha256=iWbD5G2mzxrLiC24OuGHCLwkgHQg80OXY00stnGaXL0,5054
39
+ schemathesis/cli/junitxml.py,sha256=_psBdqGwH4OKySSWeva41mbgGLav86UnWhQyOt99gnU,5331
40
40
  schemathesis/cli/options.py,sha256=yL7nrzKkbGCc4nQya9wpTW48XGz_OT9hOFrzPxRrDe4,2853
41
41
  schemathesis/cli/reporting.py,sha256=KC3sxSc1u4aFQ-0Q8CQ3G4HTEl7QxlubGnJgNKmVJdQ,3627
42
42
  schemathesis/cli/sanitization.py,sha256=Onw_NWZSom6XTVNJ5NHnC0PAhrYAcGzIXJbsBCzLkn4,1005
@@ -107,7 +107,7 @@ schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzM
107
107
  schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
108
108
  schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
109
109
  schemathesis/specs/openapi/_hypothesis.py,sha256=nU8UDn1PzGCre4IVmwIuO9-CZv1KJe1fYY0d2BojhSo,22981
110
- schemathesis/specs/openapi/checks.py,sha256=VqrgvUbD8dUULxozjcuHZqP6sLrhEeP47Rz2EAQz84Q,25104
110
+ schemathesis/specs/openapi/checks.py,sha256=fWtI7rq4wi6qK2E9U_hdJlIyIBVTCB4Klh_3hrCNsps,25349
111
111
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
112
112
  schemathesis/specs/openapi/converter.py,sha256=Yxw9lS_JKEyi-oJuACT07fm04bqQDlAu-iHwzkeDvE4,3546
113
113
  schemathesis/specs/openapi/definitions.py,sha256=WTkWwCgTc3OMxfKsqh6YDoGfZMTThSYrHGp8h0vLAK0,93935
@@ -119,7 +119,7 @@ schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9
119
119
  schemathesis/specs/openapi/parameters.py,sha256=LUahlWKCDSlp94v2IA1Q90pyeECgO6FmrqbzCU-9Z0Y,14658
120
120
  schemathesis/specs/openapi/patterns.py,sha256=aEOiJeqI_qcE9bE2Viz6TUA8UppiTHm6QFxrLJryag8,5520
121
121
  schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
122
- schemathesis/specs/openapi/schemas.py,sha256=MLU2h9DrQNCDkk74MFFSj-8BsKjkJsf9lJQHPxLFVps,53845
122
+ schemathesis/specs/openapi/schemas.py,sha256=JA9SiBnwYg75kYnd4_0CWOuQv_XTfYwuDeGmFe4RtVo,53724
123
123
  schemathesis/specs/openapi/security.py,sha256=Z-6pk2Ga1PTUtBe298KunjVHsNh5A-teegeso7zcPIE,7138
124
124
  schemathesis/specs/openapi/serialization.py,sha256=5qGdFHZ3n80UlbSXrO_bkr4Al_7ci_Z3aSUjZczNDQY,11384
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.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,,
156
+ schemathesis-3.39.2.dist-info/METADATA,sha256=mrOeTevX0mrdFm-n_sjh0MIHgKaOS-QCk8KvPDMOxL0,12956
157
+ schemathesis-3.39.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
158
+ schemathesis-3.39.2.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
159
+ schemathesis-3.39.2.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
160
+ schemathesis-3.39.2.dist-info/RECORD,,