schemathesis 3.27.1__py3-none-any.whl → 3.28.1__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.
@@ -55,6 +55,8 @@ class ExecutionContext:
55
55
  report: ServiceReportContext | FileReportContext | None = None
56
56
  probes: list[ProbeRun] | None = None
57
57
  analysis: Result[AnalysisResult, Exception] | None = None
58
+ # Special flag to display a warning about Windows-specific encoding issue
59
+ encountered_windows_encoding_issue: bool = False
58
60
 
59
61
  @deprecated_property(removed_in="4.0", replacement="show_trace")
60
62
  def show_errors_tracebacks(self) -> bool:
@@ -1,19 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- import base64
4
3
  import platform
5
4
  import textwrap
6
5
  from dataclasses import dataclass, field
7
- from typing import TYPE_CHECKING
6
+ from typing import TYPE_CHECKING, cast
8
7
 
9
8
  from junit_xml import TestCase, TestSuite, to_xml_report_file
10
9
 
10
+ from ..exceptions import RuntimeErrorType, prepare_response_payload
11
11
  from ..models import Status
12
12
  from ..runner import events
13
13
  from ..runner.serialization import SerializedCheck, SerializedError
14
- from ..exceptions import prepare_response_payload, RuntimeErrorType
15
14
  from .handlers import EventHandler
16
- from .reporting import group_by_case, TEST_CASE_ID_TITLE, split_traceback, get_runtime_error_suggestion
15
+ from .reporting import TEST_CASE_ID_TITLE, get_runtime_error_suggestion, group_by_case, split_traceback
17
16
 
18
17
  if TYPE_CHECKING:
19
18
  from click.utils import LazyFile
@@ -65,14 +64,15 @@ def build_failure_message(idx: int, code_sample: str, checks: list[SerializedChe
65
64
  status_code = check.response.status_code
66
65
  reason = get_reason(status_code)
67
66
  message += f"\n[{check.response.status_code}] {reason}:\n"
68
- response_body = check.response.body
69
- if response_body is not None:
70
- if not response_body:
67
+ if check.response.body is not None:
68
+ if not check.response.body:
71
69
  message += "\n <EMPTY>\n"
72
70
  else:
73
71
  encoding = check.response.encoding or "utf8"
74
72
  try:
75
- payload = base64.b64decode(response_body).decode(encoding)
73
+ # Checked that is not None
74
+ body = cast(bytes, check.response.deserialize_body())
75
+ payload = body.decode(encoding)
76
76
  payload = prepare_response_payload(payload)
77
77
  payload = textwrap.indent(f"\n`{payload}`\n", prefix=" ")
78
78
  message += payload
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import base64
4
3
  import os
5
4
  import platform
6
5
  import shutil
@@ -200,6 +199,7 @@ def display_full_traceback_message(error: SerializedError) -> bool:
200
199
  "hypothesis.errors.FailedHealthCheck",
201
200
  "hypothesis.errors.InvalidArgument: Scalar ",
202
201
  "hypothesis.errors.InvalidArgument: min_size=",
202
+ "hypothesis.errors.Unsatisfiable",
203
203
  )
204
204
  )
205
205
  and "can never generate an example, because min_size is larger than Hypothesis supports." not in error.exception
@@ -303,23 +303,38 @@ def display_failures_for_single_test(context: ExecutionContext, result: Serializ
303
303
  reason = get_reason(status_code)
304
304
  response = bold(f"[{check.response.status_code}] {reason}")
305
305
  click.echo(f"\n{response}:")
306
- response_body = check.response.body
307
- if response_body is not None:
308
- if not response_body:
306
+ if check.response.body is not None:
307
+ if not check.response.body:
309
308
  click.echo("\n <EMPTY>")
310
309
  else:
311
310
  encoding = check.response.encoding or "utf8"
312
311
  try:
313
- payload = base64.b64decode(response_body).decode(encoding)
312
+ # Checked that is not None
313
+ body = cast(bytes, check.response.deserialize_body())
314
+ payload = body.decode(encoding)
314
315
  payload = prepare_response_payload(payload)
315
316
  payload = textwrap.indent(f"\n`{payload}`", prefix=" ")
316
317
  click.echo(payload)
317
318
  except UnicodeDecodeError:
318
319
  click.echo("\n <BINARY>")
319
320
 
320
- click.echo(
321
- f"\n{bold('Reproduce with')}: \n\n {code_sample}\n",
322
- )
321
+ try:
322
+ click.echo(
323
+ f"\n{bold('Reproduce with')}: \n\n {code_sample}\n",
324
+ )
325
+ except UnicodeEncodeError:
326
+ # On Windows it may fail when redirecting the output to a file
327
+ # because it uses a different encoding than the console encoding and the default
328
+ # is cp1252, which doesn't support some Unicode characters.
329
+ # In this case, display a stub message and set a flag to display a warning at the end
330
+ if platform.system() != "Windows":
331
+ raise
332
+ click.echo(
333
+ f"\n{bold('Reproduce with')}: \n\n"
334
+ " CAN NOT DISPLAY THIS CODE SAMPLE DUE TO TERMINAL LIMITATIONS.\n"
335
+ " SEE DETAILS AT THE END OF THE OUTPUT\n",
336
+ )
337
+ context.encountered_windows_encoding_issue = True
323
338
 
324
339
 
325
340
  def display_application_logs(context: ExecutionContext, event: events.Finished) -> None:
@@ -465,6 +480,17 @@ def display_statistic(context: ExecutionContext, event: events.Finished) -> None
465
480
  seed_option = f"`--hypothesis-seed={context.seed}`"
466
481
  click.secho(f"\n{bold('Note')}: To replicate these test failures, rerun with {bold(seed_option)}")
467
482
 
483
+ if context.encountered_windows_encoding_issue:
484
+ click.echo()
485
+ title = click.style("WARNING:", bold=True, fg="yellow")
486
+ name = click.style("PYTHONIOENCODING", bold=True)
487
+ value = click.style("utf8", bold=True)
488
+ warning = (
489
+ "Some code samples could not be displayed due to terminal limitations on Windows.\n"
490
+ f"To resolve this set the '{name}' environment variable to '{value}' and rerun your command."
491
+ )
492
+ click.secho(f"{title} {warning}")
493
+
468
494
  if context.report is not None and not context.is_interrupted:
469
495
  if isinstance(context.report, FileReportContext):
470
496
  click.echo()
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import base64
4
3
  from itertools import groupby
5
4
  from typing import Callable, Generator
6
5
 
@@ -22,11 +21,10 @@ def group_by_case(
22
21
 
23
22
 
24
23
  def _by_unique_code_sample(check: SerializedCheck, code_sample_style: CodeSampleStyle) -> str:
25
- request_body = base64.b64decode(check.example.body).decode() if check.example.body is not None else None
26
24
  return code_sample_style.generate(
27
25
  method=check.example.method,
28
26
  url=check.example.url,
29
- body=request_body,
27
+ body=check.example.deserialize_body(),
30
28
  headers=check.example.headers,
31
29
  verify=check.example.verify,
32
30
  extra_headers=check.example.extra_headers,
@@ -336,6 +336,12 @@ class DeadlineExceeded(Exception):
336
336
  )
337
337
 
338
338
 
339
+ class RecursiveReferenceError(Exception):
340
+ """Recursive reference is impossible to resolve due to current limitations."""
341
+
342
+ __module__ = "builtins"
343
+
344
+
339
345
  @enum.unique
340
346
  class RuntimeErrorType(str, enum.Enum):
341
347
  # Connection related issues
@@ -354,6 +360,7 @@ class RuntimeErrorType(str, enum.Enum):
354
360
 
355
361
  SCHEMA_BODY_IN_GET_REQUEST = "schema_body_in_get_request"
356
362
  SCHEMA_INVALID_REGULAR_EXPRESSION = "schema_invalid_regular_expression"
363
+ SCHEMA_UNSUPPORTED = "schema_unsupported"
357
364
  SCHEMA_GENERIC = "schema_generic"
358
365
 
359
366
  SERIALIZATION_NOT_POSSIBLE = "serialization_not_possible"
@@ -367,6 +374,7 @@ class RuntimeErrorType(str, enum.Enum):
367
374
  return self not in (
368
375
  RuntimeErrorType.SCHEMA_BODY_IN_GET_REQUEST,
369
376
  RuntimeErrorType.SCHEMA_INVALID_REGULAR_EXPRESSION,
377
+ RuntimeErrorType.SCHEMA_UNSUPPORTED,
370
378
  RuntimeErrorType.SCHEMA_GENERIC,
371
379
  RuntimeErrorType.SERIALIZATION_NOT_POSSIBLE,
372
380
  )
schemathesis/models.py CHANGED
@@ -50,11 +50,11 @@ from .exceptions import (
50
50
  from .generation import DataGenerationMethod, GenerationConfig, generate_random_case_id
51
51
  from .hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher, dispatch
52
52
  from .internal.copy import fast_deepcopy
53
- from .internal.deprecation import deprecated_property, deprecated_function
53
+ from .internal.deprecation import deprecated_function, deprecated_property
54
54
  from .parameters import Parameter, ParameterSet, PayloadAlternatives
55
55
  from .sanitization import sanitize_request, sanitize_response
56
56
  from .serializers import Serializer
57
- from .transports import ASGITransport, RequestsTransport, WSGITransport, serialize_payload
57
+ from .transports import ASGITransport, RequestsTransport, WSGITransport, serialize_payload, deserialize_payload
58
58
  from .types import Body, Cookies, FormData, Headers, NotSet, PathParameters, Query
59
59
 
60
60
  if TYPE_CHECKING:
@@ -854,6 +854,14 @@ class Request:
854
854
  body=serialize_payload(body) if body is not None else body,
855
855
  )
856
856
 
857
+ def deserialize_body(self) -> bytes | None:
858
+ """Deserialize the request body.
859
+
860
+ `Request` should be serializable to JSON, therefore body is encoded as base64 string
861
+ to support arbitrary binary data.
862
+ """
863
+ return deserialize_payload(self.body)
864
+
857
865
 
858
866
  @dataclass(repr=False)
859
867
  class Response:
@@ -923,6 +931,14 @@ class Response:
923
931
  verify=True,
924
932
  )
925
933
 
934
+ def deserialize_body(self) -> bytes | None:
935
+ """Deserialize the response body.
936
+
937
+ `Response` should be serializable to JSON, therefore body is encoded as base64 string
938
+ to support arbitrary binary data.
939
+ """
940
+ return deserialize_payload(self.body)
941
+
926
942
 
927
943
  @dataclass
928
944
  class Interaction:
@@ -42,10 +42,12 @@ from ...constants import (
42
42
  from ...exceptions import (
43
43
  CheckFailed,
44
44
  DeadlineExceeded,
45
+ InternalError,
45
46
  InvalidHeadersExample,
46
47
  InvalidRegularExpression,
47
48
  NonCheckError,
48
49
  OperationSchemaError,
50
+ RecursiveReferenceError,
49
51
  SerializationNotPossible,
50
52
  SkipTest,
51
53
  format_exception,
@@ -474,13 +476,25 @@ def run_test(
474
476
  except SkipTest as exc:
475
477
  status = Status.skip
476
478
  result.mark_skipped(exc)
477
- except AssertionError: # comes from `hypothesis-jsonschema`
478
- error = reraise(operation)
479
+ except AssertionError as exc: # May come from `hypothesis-jsonschema` or `hypothesis`
479
480
  status = Status.error
481
+ try:
482
+ operation.schema.validate()
483
+ try:
484
+ raise InternalError(f"Unexpected error during testing of this API operation: {exc}") from exc
485
+ except InternalError as exc:
486
+ error = exc
487
+ except ValidationError as exc:
488
+ error = OperationSchemaError.from_jsonschema_error(
489
+ exc,
490
+ path=operation.path,
491
+ method=operation.method,
492
+ full_path=operation.schema.get_full_path(operation.path),
493
+ )
480
494
  result.add_error(error)
481
495
  except HypothesisRefResolutionError:
482
496
  status = Status.error
483
- result.add_error(hypothesis.errors.Unsatisfiable(RECURSIVE_REFERENCE_ERROR_MESSAGE))
497
+ result.add_error(RecursiveReferenceError(RECURSIVE_REFERENCE_ERROR_MESSAGE))
484
498
  except InvalidArgument as error:
485
499
  status = Status.error
486
500
  message = get_invalid_regular_expression_message(warnings)
@@ -614,16 +628,6 @@ def get_invalid_regular_expression_message(warnings: list[WarningMessage]) -> st
614
628
  return None
615
629
 
616
630
 
617
- def reraise(operation: APIOperation) -> OperationSchemaError:
618
- try:
619
- operation.schema.validate()
620
- except ValidationError as exc:
621
- return OperationSchemaError.from_jsonschema_error(
622
- exc, path=operation.path, method=operation.method, full_path=operation.schema.get_full_path(operation.path)
623
- )
624
- return OperationSchemaError("Unknown schema error")
625
-
626
-
627
631
  MEMORY_ADDRESS_RE = re.compile("0x[0-9a-fA-F]+")
628
632
  URL_IN_ERROR_MESSAGE_RE = re.compile(r"Max retries exceeded with url: .*? \(Caused by")
629
633
 
@@ -19,6 +19,7 @@ from ..exceptions import (
19
19
  InternalError,
20
20
  InvalidRegularExpression,
21
21
  OperationSchemaError,
22
+ RecursiveReferenceError,
22
23
  RuntimeErrorType,
23
24
  SerializationError,
24
25
  UnboundPrefixError,
@@ -27,7 +28,7 @@ from ..exceptions import (
27
28
  make_unique_by_key,
28
29
  )
29
30
  from ..models import Case, Check, Interaction, Request, Response, Status, TestResult
30
- from ..transports import serialize_payload
31
+ from ..transports import serialize_payload, deserialize_payload
31
32
 
32
33
  if TYPE_CHECKING:
33
34
  import hypothesis.errors
@@ -81,6 +82,14 @@ class SerializedCase:
81
82
  extra_headers=request_data.headers,
82
83
  )
83
84
 
85
+ def deserialize_body(self) -> bytes | None:
86
+ """Deserialize the test case body.
87
+
88
+ `SerializedCase` should be serializable to JSON, therefore body is encoded as base64 string
89
+ to support arbitrary binary data.
90
+ """
91
+ return deserialize_payload(self.body)
92
+
84
93
 
85
94
  def _serialize_body(body: str | bytes | None) -> str | None:
86
95
  if body is None:
@@ -244,6 +253,11 @@ class SerializedError:
244
253
  type_ = RuntimeErrorType.HYPOTHESIS_DEADLINE_EXCEEDED
245
254
  message = str(exception).strip()
246
255
  extras = []
256
+ elif isinstance(exception, RecursiveReferenceError):
257
+ type_ = RuntimeErrorType.SCHEMA_UNSUPPORTED
258
+ message = str(exception).strip()
259
+ extras = []
260
+ title = "Unsupported Schema"
247
261
  elif isinstance(exception, hypothesis.errors.InvalidArgument) and str(exception).startswith("Scalar "):
248
262
  # Comes from `hypothesis-graphql`
249
263
  scalar_name = _scalar_name_from_error(exception)
@@ -264,6 +278,7 @@ class SerializedError:
264
278
  message = f"{exception}. Possible reasons:"
265
279
  extras = [
266
280
  "- Contradictory schema constraints, such as a minimum value exceeding the maximum.",
281
+ "- Invalid schema definitions for headers or cookies, for example allowing for non-ASCII characters.",
267
282
  "- Excessive schema complexity, which hinders parameter generation.",
268
283
  ]
269
284
  title = "Schema Error"
@@ -241,7 +241,7 @@ def _encode_multipart(value: Any, boundary: str) -> bytes:
241
241
  return body.getvalue()
242
242
 
243
243
 
244
- @register("multipart/form-data")
244
+ @register("multipart/form-data", aliases=("multipart/mixed",))
245
245
  class MultipartSerializer:
246
246
  def as_requests(self, context: SerializerContext, value: Any) -> dict[str, Any]:
247
247
  if isinstance(value, bytes):
@@ -4,22 +4,22 @@ Based on https://swagger.io/docs/specification/links/
4
4
  """
5
5
 
6
6
  from __future__ import annotations
7
+
7
8
  from dataclasses import dataclass, field
8
9
  from difflib import get_close_matches
9
- from typing import Any, Generator, NoReturn, Sequence, Union, TYPE_CHECKING
10
+ from typing import TYPE_CHECKING, Any, Generator, NoReturn, Sequence, Union
10
11
 
12
+ from ...constants import NOT_SET
13
+ from ...internal.copy import fast_deepcopy
11
14
  from ...models import APIOperation, Case
12
15
  from ...parameters import ParameterSet
13
- from ...stateful import ParsedData, StatefulTest
16
+ from ...stateful import ParsedData, StatefulTest, UnresolvableLink
14
17
  from ...stateful.state_machine import Direction
15
18
  from ...types import NotSet
16
-
17
- from ...constants import NOT_SET
18
- from ...internal.copy import fast_deepcopy
19
19
  from . import expressions
20
20
  from .constants import LOCATION_TO_CONTAINER
21
21
  from .parameters import OpenAPI20Body, OpenAPI30Body, OpenAPIParameter
22
-
22
+ from .references import Unresolvable
23
23
 
24
24
  if TYPE_CHECKING:
25
25
  from ...transports.responses import GenericResponse
@@ -61,16 +61,17 @@ class Link(StatefulTest):
61
61
  def parse(self, case: Case, response: GenericResponse) -> ParsedData:
62
62
  """Parse data into a structure expected by links definition."""
63
63
  context = expressions.ExpressionContext(case=case, response=response)
64
- parameters = {
65
- parameter: expressions.evaluate(expression, context) for parameter, expression in self.parameters.items()
66
- }
67
- return ParsedData(
68
- parameters=parameters,
69
- # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#link-object
70
- # > A literal value or {expression} to use as a request body when calling the target operation.
71
- # In this case all literals will be passed as is, and expressions will be evaluated
72
- body=expressions.evaluate(self.request_body, context),
73
- )
64
+ parameters = {}
65
+ for parameter, expression in self.parameters.items():
66
+ evaluated = expressions.evaluate(expression, context)
67
+ if isinstance(evaluated, Unresolvable):
68
+ raise UnresolvableLink(f"Unresolvable reference in the link: {expression}")
69
+ parameters[parameter] = evaluated
70
+ # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#link-object
71
+ # > A literal value or {expression} to use as a request body when calling the target operation.
72
+ # In this case all literals will be passed as is, and expressions will be evaluated
73
+ body = expressions.evaluate(self.request_body, context)
74
+ return ParsedData(parameters=parameters, body=body)
74
75
 
75
76
  def make_operation(self, collected: list[ParsedData]) -> APIOperation:
76
77
  """Create a modified version of the original API operation with additional data merged in."""
@@ -186,7 +186,7 @@ def remove_optional_references(schema: dict[str, Any]) -> None:
186
186
  v = s.get(keyword)
187
187
  if v is not None:
188
188
  elided = [sub for sub in v if not can_elide(sub)]
189
- if len(elided) == 1 and "$ref" in elided[0]:
189
+ if len(elided) == 1 and contains_ref(elided[0]):
190
190
  found.append(keyword)
191
191
  return found
192
192
 
@@ -524,7 +524,7 @@ class BaseOpenAPISchema(BaseSchema):
524
524
  if operation == source.path:
525
525
  # Methods should be completely resolved now, otherwise they might miss a resolving scope when
526
526
  # they will be fully resolved later
527
- methods = self.resolver.resolve_all(methods)
527
+ methods = self.resolver.resolve_all(methods, RECURSION_DEPTH_LIMIT - 8)
528
528
  found = False
529
529
  for method, definition in methods.items():
530
530
  if method.upper() == source.method.upper():
@@ -1065,12 +1065,12 @@ class OpenApi30(SwaggerV20):
1065
1065
  # the "multipart/form-data" media type, or any other more general media type that matches it (like `*/*`)
1066
1066
  for media_type, entry in content.items():
1067
1067
  main, sub = parse_content_type(media_type)
1068
- if main in ("*", "multipart") and sub in ("*", "form-data"):
1069
- schema = entry["schema"]
1068
+ if main in ("*", "multipart") and sub in ("*", "form-data", "mixed"):
1069
+ schema = entry.get("schema")
1070
1070
  break
1071
1071
  else:
1072
1072
  raise InternalError("No 'multipart/form-data' media type found in the schema")
1073
- for name, property_schema in schema.get("properties", {}).items():
1073
+ for name, property_schema in (schema or {}).get("properties", {}).items():
1074
1074
  if name in form_data:
1075
1075
  if isinstance(form_data[name], list):
1076
1076
  files.extend([(name, item) for item in form_data[name]])
@@ -10,8 +10,7 @@ from ....internal.result import Ok
10
10
  from ....stateful.state_machine import APIStateMachine, Direction, StepResult
11
11
  from ....utils import combine_strategies
12
12
  from .. import expressions
13
- from . import links
14
- from .links import APIOperationConnections, Connection, _convert_strategy, apply, make_response_filter
13
+ from .links import APIOperationConnections, Connection, apply
15
14
 
16
15
  if TYPE_CHECKING:
17
16
  from ....models import APIOperation, Case
@@ -16,6 +16,10 @@ if TYPE_CHECKING:
16
16
  from .state_machine import APIStateMachine
17
17
 
18
18
 
19
+ class UnresolvableLink(Exception):
20
+ """Raised when a link cannot be resolved."""
21
+
22
+
19
23
  @enum.unique
20
24
  class Stateful(enum.Enum):
21
25
  none = 1
@@ -70,8 +74,12 @@ class StatefulData:
70
74
 
71
75
  def store(self, case: Case, response: GenericResponse) -> None:
72
76
  """Parse and store data for a stateful test."""
73
- parsed = self.stateful_test.parse(case, response)
74
- self.container.append(parsed)
77
+ try:
78
+ parsed = self.stateful_test.parse(case, response)
79
+ self.container.append(parsed)
80
+ except UnresolvableLink:
81
+ # For now, ignore if a link cannot be resolved
82
+ pass
75
83
 
76
84
 
77
85
  @dataclass
@@ -30,6 +30,12 @@ def serialize_payload(payload: bytes) -> str:
30
30
  return base64.b64encode(payload).decode()
31
31
 
32
32
 
33
+ def deserialize_payload(data: str | None) -> bytes | None:
34
+ if data is None:
35
+ return None
36
+ return base64.b64decode(data)
37
+
38
+
33
39
  def get(app: Any) -> Transport:
34
40
  """Get transport to send the data to the application."""
35
41
  if app is None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: schemathesis
3
- Version: 3.27.1
3
+ Version: 3.28.1
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
@@ -77,7 +77,7 @@ Requires-Dist: trustme<1.0,>=0.9.0; extra == 'tests'
77
77
  Description-Content-Type: text/markdown
78
78
 
79
79
  <p align="center">
80
- <em>Schemathesis: catch crashes, validate specs, and save time</em>
80
+ <em>Schemathesis: Supercharge your API testing, catch bugs, and ensure compliance</em>
81
81
  </p>
82
82
 
83
83
  <p align="center">
@@ -109,29 +109,34 @@ Description-Content-Type: text/markdown
109
109
 
110
110
  ---
111
111
 
112
- ## Why Schemathesis?
112
+ ## What is Schemathesis?
113
113
 
114
- Schemathesis is a tool that automates your API testing to catch crashes and spec violations. Built on top of the widely-used <a href="https://hypothesis.works/" target="_blank">Hypothesis</a> framework for property-based testing, it offers the following advantages:
114
+ Schemathesis is a tool that levels-up your API testing by automating the process of finding crashes, uncovering bugs, and validating spec compliance. With Schemathesis, you can:
115
115
 
116
- 🕒 **Time-Saving**
116
+ 🎯 **Catch Hard-to-Find Bugs**
117
117
 
118
- Automatically generates test cases, freeing you from manual test writing.
118
+ - Uncover hidden crashes and edge cases that manual testing might miss
119
+ - Identify spec violations and ensure your API adheres to its defined contract
119
120
 
120
- 🔍 **Comprehensive**
121
+ **Accelerate Testing Cycles**
121
122
 
122
- Utilizes fuzzing techniques to probe both common and edge-case scenarios, including those you might overlook.
123
+ - Automatically generate a wide range of test cases based on your API schema
124
+ - Save time and effort by eliminating the need for manual test case creation
123
125
 
124
- 🛠️ **Flexible**
126
+ 🧩 **Integrate Seamlessly**
125
127
 
126
- Supports OpenAPI, GraphQL, and can work even with partially complete schemas. Only the parts describing data generation or responses are required.
128
+ - Works with popular API formats such as OpenAPI, GraphQL.
129
+ - Easily integrate into your existing testing pipeline and CI/CD workflows
127
130
 
128
- 🎛️ **Customizable**
131
+ 🔧 **Customize and Extend**
129
132
 
130
- Customize the framework by writing Python extensions to modify almost any aspect of the testing process.
133
+ - Tune the testing process to your specific requirements using Python extensions
134
+ - Modify and enhance various aspects of the testing flow to suit your needs with rich configuration options
131
135
 
132
- 🔄 **Reproducible**
136
+ 📊 **Gain Valuable Insights**
133
137
 
134
- Generates code samples to help you quickly replicate and investigate any failing test cases.
138
+ - Get detailed reports and actionable insights to help you identify and fix issues quickly
139
+ - Reproduce failing test cases effortlessly with generated code samples and cURL commands
135
140
 
136
141
  ## Quick Demo
137
142
 
@@ -309,6 +314,24 @@ This includes:
309
314
 
310
315
  To discuss a custom support arrangement that best suits your organization, please contact our support team at <a href="mailto:support@schemathesis.io">support@schemathesis.io</a>.
311
316
 
317
+ ## Acknowledgements
318
+
319
+ Schemathesis is built on top of <a href="https://hypothesis.works/" target="_blank">Hypothesis</a>, a powerful property-based testing library for Python.
320
+
321
+ ## Who's Using Schemathesis?
322
+
323
+ Schemathesis is used by a number of project and companies, including direct usage or integration into other tools:
324
+
325
+ - Abstract Machines ([Magistrala](https://github.com/absmach/magistrala))
326
+ - Bundesstelle für Open Data ([smard-api](https://github.com/bundesAPI/smard-api))
327
+ - [CheckMK](https://github.com/Checkmk/checkmk)
328
+ - HXSecurity ([DongTai](https://github.com/HXSecurity/DongTai))
329
+ - Netflix ([Dispatch](https://github.com/Netflix/dispatch))
330
+ - [Pixie](https://github.com/pixie-io/pixie)
331
+ - [Qdrant](https://github.com/qdrant/qdrant)
332
+ - Spotify ([Backstage](https://github.com/backstage/backstage))
333
+ - WordPress ([OpenVerse](https://github.com/WordPress/openverse))
334
+
312
335
  ## Additional content
313
336
 
314
337
  ### Papers
@@ -10,19 +10,19 @@ schemathesis/auths.py,sha256=tUuaHvXO96HJr22Gu9OmlSpL2wbAqKZr3TRbb9dVyA4,14732
10
10
  schemathesis/checks.py,sha256=SYts1Teecg-5kSHBo32Pzhh7YQ4a1Y7DIfldd-0VTj8,2155
11
11
  schemathesis/code_samples.py,sha256=QFwyA7dmc_ij2FRwfbe0uMRm6j2yeofQ8zXynZV3SZI,4122
12
12
  schemathesis/constants.py,sha256=gptQZnXJwEJjuLgw62n7LL_kbkE7dhxUgyyJw_dEEAc,2672
13
- schemathesis/exceptions.py,sha256=fAkismvT-QcIunr6rZ6OllhXFK3sHjwiZJlD63fttQw,19631
13
+ schemathesis/exceptions.py,sha256=-u7pnntcv2OBuY0aO4SGRRtGk2Bd8RqdZuUTSnBzZjk,19882
14
14
  schemathesis/failures.py,sha256=oKbIDD075ymLT4h7SQK-herHDT608d-StPX9pXmOSVU,5996
15
15
  schemathesis/filters.py,sha256=9SJyZ8mcuDyKG2G1hp2Nfz-TvyJWP_NcntsgwFWYY_Q,10250
16
16
  schemathesis/graphql.py,sha256=YkoKWY5K8lxp7H3ikAs-IsoDbiPwJvChG7O8p3DgwtI,229
17
17
  schemathesis/hooks.py,sha256=cNJgCh7SyLWT1sYDKF5ncDC80ld08CinvKo2IqLMV4g,12396
18
18
  schemathesis/lazy.py,sha256=CivWpvesh4iYLSkatXbQPTEOruWmXvuZQ29gng5p9wM,14846
19
19
  schemathesis/loaders.py,sha256=RJnrbf-3vZ7KXmRBkmr3uqWyg0eHzOnONABuudWcTIg,4586
20
- schemathesis/models.py,sha256=KtWx7k9M8wrkaCZ82kfLMKd7jmvFxZnGyUuTE_nPhU0,42974
20
+ schemathesis/models.py,sha256=K5Ct8NhpN0UmjNRVLnb5W_d5tMCu0IYZxR3Q8oE6L4Y,43565
21
21
  schemathesis/parameters.py,sha256=VheEffVzoSfYaSEcG7KhPlA4ypifosG8biiHifzwL8g,2257
22
22
  schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  schemathesis/sanitization.py,sha256=WSV_MB5YRrYkp1pQRPHrzsidqsKcqYZiq64N9grKobo,8956
24
24
  schemathesis/schemas.py,sha256=RzxAoBoJJSLsixrkTZPe2N_oWbWv8n5RL4XaYRyoSIo,18531
25
- schemathesis/serializers.py,sha256=_xoebWkVrgbGbPjPgTgwuN-fN4YT004aj7kImyPComY,11619
25
+ schemathesis/serializers.py,sha256=kxXZ-UGa1v_vOm0sC4QYcrNv4rfvI7tHGT2elRVbCbc,11649
26
26
  schemathesis/targets.py,sha256=tzp7VZ2-7g2nZHCooRgFzTMtOVcbl0rvtNR421hQthA,1162
27
27
  schemathesis/throttling.py,sha256=uwhL4XWPWAU8HECg0NhibfCMn5dT7NElTx3fdL3Mmcc,1065
28
28
  schemathesis/types.py,sha256=AglR5M0bce-YXeDRkweToXTP0GjNOWVjS_mIsxLobwc,919
@@ -32,15 +32,15 @@ schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,
32
32
  schemathesis/cli/callbacks.py,sha256=HmD0WmSYf5x8v4xeZdOKzy_8CEk23NitUlL8JQ7LHdQ,14950
33
33
  schemathesis/cli/cassettes.py,sha256=1ucgYyOZiNu44uOv044FdfKTzQolUyhAM2onk4m3MD0,12988
34
34
  schemathesis/cli/constants.py,sha256=XoezT0_fHuiOY2e--cmBUhNieJsepcUoW8e48QuLSDI,1544
35
- schemathesis/cli/context.py,sha256=wwfdSL_NKHEyzQnj8PcnaJdzasa_N08e2KPvy4Wl8N4,1874
35
+ schemathesis/cli/context.py,sha256=Hsbt7YS1Gr01QJ825mECpEduEbRIaRRMUz7k6j1SpVI,2005
36
36
  schemathesis/cli/debug.py,sha256=PDEa-oHyz5bZ8aYjRYARwQaCv_AC6HM_L43LJfe4vUM,657
37
37
  schemathesis/cli/handlers.py,sha256=62GPWPmgeGUz3pkDd01H4UCXcIy5a9yRtU7qaAeeR-A,389
38
- schemathesis/cli/junitxml.py,sha256=JMMtWz7grPQw66Ruh-tLR8YVn-58Q0o0l_MxZWQ4V3M,4586
38
+ schemathesis/cli/junitxml.py,sha256=WdxtBKBi6V5Ow840Nde21MvUlDJQyJu2M6DXx1VDuyo,4648
39
39
  schemathesis/cli/options.py,sha256=7_dXcrPT0kWqAkm60cAT1J0IsTOcvNFxT1pcHYttBuI,2558
40
- schemathesis/cli/reporting.py,sha256=uTtoEJT4maJRGPsVZH184S0hohX-4SwO_dAL__i6H10,3401
40
+ schemathesis/cli/reporting.py,sha256=_Jh8sJMfOf_CUHdbTpG5VRsfW8INtXQjtk1njmGWEnY,3298
41
41
  schemathesis/cli/sanitization.py,sha256=v-9rsMCBpWhom0Bfzj_8c6InqxkVjSWK6kRoURa52Nk,727
42
42
  schemathesis/cli/output/__init__.py,sha256=AXaUzQ1nhQ-vXhW4-X-91vE2VQtEcCOrGtQXXNN55iQ,29
43
- schemathesis/cli/output/default.py,sha256=GjT-j9pNxVYQmU6nOu08hBqvR9uD_TMI15jZitWgI0A,36781
43
+ schemathesis/cli/output/default.py,sha256=130qttjuzUgYcCD-LnTaMTumoOMWLzu3q-aPOyJ0ogs,38176
44
44
  schemathesis/cli/output/short.py,sha256=MmFMOKBRqqvgq7Kmx6XJc7Vqkr9g5BrJBxRbyZ0Rh-k,2068
45
45
  schemathesis/contrib/__init__.py,sha256=FH8NL8NXgSKBFOF8Jy_EB6T4CJEaiM-tmDhz16B2o4k,187
46
46
  schemathesis/contrib/unique_data.py,sha256=zxrmAlQH7Bcil9YSfy2EazwLj2rxLzOvAE3O6QRRkFY,1317
@@ -69,9 +69,9 @@ schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QA
69
69
  schemathesis/runner/__init__.py,sha256=kkqjC_5G2Mrq00syLNUDHP3sXqwiId8_cusKtIlOyXM,21419
70
70
  schemathesis/runner/events.py,sha256=eZc4TT8C8mWCMr9h4JvlijvQQ-x8wFry8LntdsYIL-A,10200
71
71
  schemathesis/runner/probes.py,sha256=t_B38-ljy-p3Odw-dqcZUVIjYTPwBvac8KE5GaLnz4M,5546
72
- schemathesis/runner/serialization.py,sha256=7DBve7E6JAbAHH5npzjME7lFnysKCc9dPXMmdIIYrNI,16667
72
+ schemathesis/runner/serialization.py,sha256=NkeXNQUvzpnTiT4TbojpUC0QXl2Gjv7quPx5HUxpJXI,17356
73
73
  schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
74
- schemathesis/runner/impl/core.py,sha256=Y8WyKEUeuEs0xi9hWmEA63a39-NO7L6I5hQRhwo9JBQ,39621
74
+ schemathesis/runner/impl/core.py,sha256=E38yRz4c5hc_QjZnMSvZwbRYjt6VpoS2KxH2V7l3RIk,39802
75
75
  schemathesis/runner/impl/solo.py,sha256=Y_tNhVBVxcuxv65hN0FjxLlVSC41ebHMOM1xSzVrNk8,3358
76
76
  schemathesis/runner/impl/threadpool.py,sha256=2-2Wvw7msezZtenZY5vU_x3FGLLVlH-ywvhU9hTwAAo,15073
77
77
  schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
@@ -103,12 +103,12 @@ schemathesis/specs/openapi/definitions.py,sha256=t5xffHLBnSgptBdDkSqYN1OfT-DaXoe
103
103
  schemathesis/specs/openapi/examples.py,sha256=igLDfMpNhRMAk7mYBqi93CtVRQTZCWjCJ2KxlQFotdA,14932
104
104
  schemathesis/specs/openapi/filters.py,sha256=Ei-QTFcGCvGSIunT-GYQrtqzB-kqvUePOcUuC7B7mT8,1436
105
105
  schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
106
- schemathesis/specs/openapi/links.py,sha256=FUjqEf6Sv6PeS0UX7CLL5p2VHa9vA1MGDPhx2pfYg1s,14576
106
+ schemathesis/specs/openapi/links.py,sha256=Afju0IzRzvDypD3_V_SauhpKuMICczck6tLqezEapYY,14794
107
107
  schemathesis/specs/openapi/loaders.py,sha256=QoEFJ7fxJz5XcGTEWV_ZGXARiektmyyT_xmVtcHco6Q,24131
108
108
  schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
109
109
  schemathesis/specs/openapi/parameters.py,sha256=5jMZQZFsZNBFjG22Bqot-Rc65emiSA4E95rIzwImThk,13610
110
- schemathesis/specs/openapi/references.py,sha256=YGunHAGubYPKbMqQtpFWZm1D4AGxB8wLuiVhE6T6cWo,8978
111
- schemathesis/specs/openapi/schemas.py,sha256=BuBKfToNYIsQ1RIh3EedNaB948-33wLSrjSi1lBV0F0,50457
110
+ schemathesis/specs/openapi/references.py,sha256=zEDzoJbUAwVYTy9CtJ9w1E1Xzp60CVsjebDkFjMXnno,8982
111
+ schemathesis/specs/openapi/schemas.py,sha256=k6tcZrSwDAC8LA_hmRv2PvQ7epNxVs9wIZ-lkquCZcs,50505
112
112
  schemathesis/specs/openapi/security.py,sha256=nCUIaZTzI6t26HAfd8YTHW6mFxXAPN9Ds-P9UnXxmNA,6628
113
113
  schemathesis/specs/openapi/serialization.py,sha256=jajqowTIiyEVWEx-Gy4ZinXZewNg0n_ipsGzz7JXP7c,11383
114
114
  schemathesis/specs/openapi/utils.py,sha256=gmW4v6o6sZQifajfPquhFeWmZWGQM89mOOBYZvjnE7g,741
@@ -123,17 +123,17 @@ schemathesis/specs/openapi/negative/__init__.py,sha256=Nhe6qeSCnlgf5l-Fu6yHvL--3
123
123
  schemathesis/specs/openapi/negative/mutations.py,sha256=SQjQR5aK-LpbSWzwrtbFX3PQ2xb5hzJV7eZXw6MlRJo,18643
124
124
  schemathesis/specs/openapi/negative/types.py,sha256=a7buCcVxNBG6ILBM3A7oNTAX0lyDseEtZndBuej8MbI,174
125
125
  schemathesis/specs/openapi/negative/utils.py,sha256=ozcOIuASufLqZSgnKUACjX-EOZrrkuNdXX0SDnLoGYA,168
126
- schemathesis/specs/openapi/stateful/__init__.py,sha256=R8RhPJD3rDzqL4eA9jSnUwh9Q_Mv27ka1C5FdRuyusY,4509
126
+ schemathesis/specs/openapi/stateful/__init__.py,sha256=XpPBe0JLK2LUPUTiLn0UE0aWrNwVsbzlhqejVXRGXJM,4448
127
127
  schemathesis/specs/openapi/stateful/links.py,sha256=cSIwho2Hroty6au-gyCD-OwqnuCcIpnIIHU6FvF0SwA,3533
128
- schemathesis/stateful/__init__.py,sha256=T7rvhzftfl3wumEsir33DBBzCTK2PtRp9CxBxMLdMSE,4693
128
+ schemathesis/stateful/__init__.py,sha256=uo0UuLPuNsf7dK401BaNA8AqvlGXn2cb2FZlhzHmXYk,4909
129
129
  schemathesis/stateful/state_machine.py,sha256=ZcKpbvEl1QGhVOYnA7Ow6zkuFHtEPDAyCjroPrj-FgU,11343
130
- schemathesis/transports/__init__.py,sha256=IHQaob4pLHPP0zXXJukWEQBpV4aSIhGPHHMm0up8x64,11287
130
+ schemathesis/transports/__init__.py,sha256=vQ5pyMefhVpbl0FFXtivGsFHWX-7V1PXNwvqCKMJHoI,11423
131
131
  schemathesis/transports/auth.py,sha256=ZKFku9gjhIG6445qNC2p_64Yt9Iz_4azbvja8AMptBk,404
132
132
  schemathesis/transports/content_types.py,sha256=xU8RZWxz-CyWRqQTI2fGYQacB7KasoY7LL_bxPQdyPY,2144
133
133
  schemathesis/transports/headers.py,sha256=EDxpm8su0AuhyqZUkMex-OFZMAJN_5NHah7fDT2HDZE,989
134
134
  schemathesis/transports/responses.py,sha256=j_-udvWbmi6XtEYmpdX8WoFnlrQ6-i3PSBizfozRjQI,1673
135
- schemathesis-3.27.1.dist-info/METADATA,sha256=GtcBreI2a4R9b3KL_eS-QQb57LTmQckmsQ_vkPTIdDs,16242
136
- schemathesis-3.27.1.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
137
- schemathesis-3.27.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
138
- schemathesis-3.27.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
139
- schemathesis-3.27.1.dist-info/RECORD,,
135
+ schemathesis-3.28.1.dist-info/METADATA,sha256=0gIx1YbzeUCbaEjNYgoVWpn92YEgfL87fr6H1ft3rG8,17376
136
+ schemathesis-3.28.1.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
137
+ schemathesis-3.28.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
138
+ schemathesis-3.28.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
139
+ schemathesis-3.28.1.dist-info/RECORD,,