schemathesis 3.30.3__py3-none-any.whl → 3.31.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.
@@ -98,7 +98,9 @@ def create_test(
98
98
  existing_settings = remove_explain_phase(existing_settings)
99
99
  wrapped_test._hypothesis_internal_use_settings = existing_settings # type: ignore
100
100
  if Phase.explicit in existing_settings.phases:
101
- wrapped_test = add_examples(wrapped_test, operation, hook_dispatcher=hook_dispatcher)
101
+ wrapped_test = add_examples(
102
+ wrapped_test, operation, hook_dispatcher=hook_dispatcher, as_strategy_kwargs=as_strategy_kwargs
103
+ )
102
104
  return wrapped_test
103
105
 
104
106
 
@@ -138,12 +140,20 @@ def make_async_test(test: Callable) -> Callable:
138
140
  return async_run
139
141
 
140
142
 
141
- def add_examples(test: Callable, operation: APIOperation, hook_dispatcher: HookDispatcher | None = None) -> Callable:
143
+ def add_examples(
144
+ test: Callable,
145
+ operation: APIOperation,
146
+ hook_dispatcher: HookDispatcher | None = None,
147
+ as_strategy_kwargs: dict[str, Any] | None = None,
148
+ ) -> Callable:
142
149
  """Add examples to the Hypothesis test, if they are specified in the schema."""
143
150
  from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
144
151
 
145
152
  try:
146
- examples: list[Case] = [get_single_example(strategy) for strategy in operation.get_strategies_from_examples()]
153
+ examples: list[Case] = [
154
+ get_single_example(strategy)
155
+ for strategy in operation.get_strategies_from_examples(as_strategy_kwargs=as_strategy_kwargs)
156
+ ]
147
157
  except (
148
158
  OperationSchemaError,
149
159
  HypothesisRefResolutionError,
@@ -522,6 +522,13 @@ The report data, consisting of a tar gz file with multiple JSON files, is subjec
522
522
  type=click.File("w", encoding="utf-8"),
523
523
  is_eager=True,
524
524
  )
525
+ @click.option(
526
+ "--cassette-format",
527
+ help="Format of the saved cassettes.",
528
+ type=click.Choice([item.name.lower() for item in cassettes.CassetteFormat]),
529
+ default=cassettes.CassetteFormat.VCR.name.lower(),
530
+ callback=callbacks.convert_cassette_format,
531
+ )
525
532
  @click.option(
526
533
  "--cassette-preserve-exact-body-bytes",
527
534
  help="Retains exact byte sequence of payloads in cassettes, encoded as base64.",
@@ -718,6 +725,14 @@ The report data, consisting of a tar gz file with multiple JSON files, is subjec
718
725
  default="utf-8",
719
726
  callback=callbacks.validate_generation_codec,
720
727
  )
728
+ @click.option(
729
+ "--generation-with-security-parameters",
730
+ help="Whether to generate security parameters.",
731
+ type=str,
732
+ default="true",
733
+ show_default=True,
734
+ callback=callbacks.convert_boolean_string,
735
+ )
721
736
  @click.option(
722
737
  "--schemathesis-io-token",
723
738
  help="Schemathesis.io authentication token.",
@@ -783,6 +798,7 @@ def run(
783
798
  show_trace: bool = False,
784
799
  code_sample_style: CodeSampleStyle = CodeSampleStyle.default(),
785
800
  cassette_path: click.utils.LazyFile | None = None,
801
+ cassette_format: cassettes.CassetteFormat = cassettes.CassetteFormat.VCR,
786
802
  cassette_preserve_exact_body_bytes: bool = False,
787
803
  store_network_log: click.utils.LazyFile | None = None,
788
804
  wait_for_schema: float | None = None,
@@ -810,6 +826,7 @@ def run(
810
826
  no_color: bool = False,
811
827
  report_value: str | None = None,
812
828
  generation_allow_x00: bool = True,
829
+ generation_with_security_parameters: bool = True,
813
830
  generation_codec: str = "utf-8",
814
831
  schemathesis_io_token: str | None = None,
815
832
  schemathesis_io_url: str = service.DEFAULT_URL,
@@ -849,7 +866,11 @@ def run(
849
866
 
850
867
  override = CaseOverride(query=set_query, headers=set_header, cookies=set_cookie, path_parameters=set_path)
851
868
 
852
- generation_config = generation.GenerationConfig(allow_x00=generation_allow_x00, codec=generation_codec)
869
+ generation_config = generation.GenerationConfig(
870
+ allow_x00=generation_allow_x00,
871
+ codec=generation_codec,
872
+ with_security_parameters=generation_with_security_parameters,
873
+ )
853
874
 
854
875
  report: ReportToService | click.utils.LazyFile | None
855
876
  if report_value is None:
@@ -1006,6 +1027,7 @@ def run(
1006
1027
  wait_for_schema=wait_for_schema,
1007
1028
  validate_schema=validate_schema,
1008
1029
  cassette_path=cassette_path,
1030
+ cassette_format=cassette_format,
1009
1031
  cassette_preserve_exact_body_bytes=cassette_preserve_exact_body_bytes,
1010
1032
  junit_xml=junit_xml,
1011
1033
  verbosity=verbosity,
@@ -1387,6 +1409,7 @@ def execute(
1387
1409
  wait_for_schema: float | None,
1388
1410
  validate_schema: bool,
1389
1411
  cassette_path: click.utils.LazyFile | None,
1412
+ cassette_format: cassettes.CassetteFormat,
1390
1413
  cassette_preserve_exact_body_bytes: bool,
1391
1414
  junit_xml: click.utils.LazyFile | None,
1392
1415
  verbosity: int,
@@ -1449,7 +1472,9 @@ def execute(
1449
1472
  # This handler should be first to have logs writing completed when the output handler will display statistic
1450
1473
  _open_file(cassette_path)
1451
1474
  handlers.append(
1452
- cassettes.CassetteWriter(cassette_path, preserve_exact_body_bytes=cassette_preserve_exact_body_bytes)
1475
+ cassettes.CassetteWriter(
1476
+ cassette_path, format=cassette_format, preserve_exact_body_bytes=cassette_preserve_exact_body_bytes
1477
+ )
1453
1478
  )
1454
1479
  handlers.append(get_output_handler(workers_num))
1455
1480
  if sanitize_output:
@@ -24,6 +24,7 @@ from ..service.hosts import get_temporary_hosts_file
24
24
  from ..stateful import Stateful
25
25
  from ..transports.headers import has_invalid_characters, is_latin_1_encodable
26
26
  from ..types import PathLike
27
+ from .cassettes import CassetteFormat
27
28
  from .constants import DEFAULT_WORKERS
28
29
 
29
30
  if TYPE_CHECKING:
@@ -344,6 +345,10 @@ def convert_code_sample_style(ctx: click.core.Context, param: click.core.Paramet
344
345
  return CodeSampleStyle.from_str(value)
345
346
 
346
347
 
348
+ def convert_cassette_format(ctx: click.core.Context, param: click.core.Parameter, value: str) -> CassetteFormat:
349
+ return CassetteFormat.from_str(value)
350
+
351
+
347
352
  def convert_data_generation_method(
348
353
  ctx: click.core.Context, param: click.core.Parameter, value: str
349
354
  ) -> list[DataGenerationMethod]:
@@ -1,13 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import base64
4
+ import enum
4
5
  import json
5
6
  import re
6
7
  import sys
7
8
  import threading
8
9
  from dataclasses import dataclass, field
10
+ from http.cookies import SimpleCookie
9
11
  from queue import Queue
10
- from typing import IO, TYPE_CHECKING, Any, Generator, Iterator, cast
12
+ from typing import IO, TYPE_CHECKING, Any, Callable, Generator, Iterator, cast
13
+ from urllib.parse import parse_qsl, urlparse
14
+
15
+ import harfile
11
16
 
12
17
  from ..constants import SCHEMATHESIS_VERSION
13
18
  from ..runner import events
@@ -27,6 +32,23 @@ if TYPE_CHECKING:
27
32
  WRITER_WORKER_JOIN_TIMEOUT = 1
28
33
 
29
34
 
35
+ class CassetteFormat(str, enum.Enum):
36
+ """Type of the cassette."""
37
+
38
+ VCR = "vcr"
39
+ HAR = "har"
40
+
41
+ @classmethod
42
+ def from_str(cls, value: str) -> CassetteFormat:
43
+ try:
44
+ return cls[value.upper()]
45
+ except KeyError:
46
+ available_formats = ", ".join(cls)
47
+ raise ValueError(
48
+ f"Invalid value for cassette format: {value}. Available formats: {available_formats}"
49
+ ) from None
50
+
51
+
30
52
  @dataclass
31
53
  class CassetteWriter(EventHandler):
32
54
  """Write interactions in a YAML cassette.
@@ -36,26 +58,30 @@ class CassetteWriter(EventHandler):
36
58
  """
37
59
 
38
60
  file_handle: click.utils.LazyFile
61
+ format: CassetteFormat
39
62
  preserve_exact_body_bytes: bool
40
63
  queue: Queue = field(default_factory=Queue)
41
64
  worker: threading.Thread = field(init=False)
42
65
 
43
66
  def __post_init__(self) -> None:
44
- self.worker = threading.Thread(
45
- target=worker,
46
- kwargs={
47
- "file_handle": self.file_handle,
48
- "preserve_exact_body_bytes": self.preserve_exact_body_bytes,
49
- "queue": self.queue,
50
- },
51
- )
67
+ kwargs = {
68
+ "file_handle": self.file_handle,
69
+ "queue": self.queue,
70
+ "preserve_exact_body_bytes": self.preserve_exact_body_bytes,
71
+ }
72
+ writer: Callable
73
+ if self.format == CassetteFormat.HAR:
74
+ writer = har_writer
75
+ else:
76
+ writer = vcr_writer
77
+ self.worker = threading.Thread(name="SchemathesisCassetteWriter", target=writer, kwargs=kwargs)
52
78
  self.worker.start()
53
79
 
54
80
  def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
55
81
  if isinstance(event, events.Initialized):
56
82
  # In the beginning we write metadata and start `http_interactions` list
57
83
  self.queue.put(Initialize())
58
- if isinstance(event, events.AfterExecution):
84
+ elif isinstance(event, events.AfterExecution):
59
85
  # Seed is always present at this point, the original Optional[int] type is there because `TestResult`
60
86
  # instance is created before `seed` is generated on the hypothesis side
61
87
  seed = cast(int, event.result.seed)
@@ -71,7 +97,19 @@ class CassetteWriter(EventHandler):
71
97
  interactions=event.result.interactions,
72
98
  )
73
99
  )
74
- if isinstance(event, events.Finished):
100
+ elif isinstance(event, events.AfterStatefulExecution):
101
+ seed = cast(int, event.result.seed)
102
+ self.queue.put(
103
+ Process(
104
+ seed=seed,
105
+ # Correlation ID is not used in stateful testing
106
+ correlation_id="",
107
+ thread_id=event.thread_id,
108
+ data_generation_method=event.data_generation_method[0],
109
+ interactions=event.result.interactions,
110
+ )
111
+ )
112
+ elif isinstance(event, events.Finished):
75
113
  self.shutdown()
76
114
 
77
115
  def shutdown(self) -> None:
@@ -112,7 +150,7 @@ def get_command_representation() -> str:
112
150
  return f"st {args}"
113
151
 
114
152
 
115
- def worker(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: bool, queue: Queue) -> None:
153
+ def vcr_writer(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: bool, queue: Queue) -> None:
116
154
  """Write YAML to a file in an incremental manner.
117
155
 
118
156
  This implementation doesn't use `pyyaml` package and composes YAML manually as string due to the following reasons:
@@ -278,6 +316,106 @@ def write_double_quoted(stream: IO, text: str) -> None:
278
316
  stream.write('"')
279
317
 
280
318
 
319
+ def har_writer(file_handle: click.utils.LazyFile, preserve_exact_body_bytes: bool, queue: Queue) -> None:
320
+ if preserve_exact_body_bytes:
321
+
322
+ def get_body(body: str) -> str:
323
+ return body
324
+ else:
325
+
326
+ def get_body(body: str) -> str:
327
+ return base64.b64decode(body).decode("utf-8", errors="replace")
328
+
329
+ with harfile.open(file_handle) as har:
330
+ while True:
331
+ item = queue.get()
332
+ if isinstance(item, Process):
333
+ for interaction in item.interactions:
334
+ time = round(interaction.response.elapsed * 1000, 2)
335
+ content_type = interaction.response.headers.get("Content-Type", [""])[0]
336
+ content = harfile.Content(
337
+ size=interaction.response.body_size or 0,
338
+ mimeType=content_type,
339
+ text=get_body(interaction.response.body) if interaction.response.body is not None else None,
340
+ encoding="base64"
341
+ if interaction.response.body is not None and preserve_exact_body_bytes
342
+ else None,
343
+ )
344
+ http_version = f"HTTP/{interaction.response.http_version}"
345
+ query_params = urlparse(interaction.request.uri).query
346
+ if interaction.request.body is not None:
347
+ post_data = harfile.PostData(
348
+ mimeType=content_type,
349
+ text=get_body(interaction.request.body),
350
+ )
351
+ else:
352
+ post_data = None
353
+ har.add_entry(
354
+ startedDateTime=interaction.recorded_at,
355
+ time=time,
356
+ request=harfile.Request(
357
+ method=interaction.request.method.upper(),
358
+ url=interaction.request.uri,
359
+ httpVersion=http_version,
360
+ headers=[
361
+ harfile.Record(name=name, value=values[0])
362
+ for name, values in interaction.request.headers.items()
363
+ ],
364
+ queryString=[
365
+ harfile.Record(name=name, value=value)
366
+ for name, value in parse_qsl(query_params, keep_blank_values=True)
367
+ ],
368
+ cookies=_extract_cookies(interaction.request.headers.get("Cookie", [])),
369
+ headersSize=_headers_size(interaction.request.headers),
370
+ bodySize=interaction.request.body_size or 0,
371
+ postData=post_data,
372
+ ),
373
+ response=harfile.Response(
374
+ status=interaction.response.status_code,
375
+ httpVersion=http_version,
376
+ statusText=interaction.response.message,
377
+ headers=[
378
+ harfile.Record(name=name, value=values[0])
379
+ for name, values in interaction.response.headers.items()
380
+ ],
381
+ cookies=_extract_cookies(interaction.response.headers.get("Set-Cookie", [])),
382
+ content=content,
383
+ headersSize=_headers_size(interaction.response.headers),
384
+ bodySize=interaction.response.body_size or 0,
385
+ redirectURL=interaction.response.headers.get("Location", [""])[0],
386
+ ),
387
+ timings=harfile.Timings(send=0, wait=0, receive=time, blocked=0, dns=0, connect=0, ssl=0),
388
+ )
389
+ elif isinstance(item, Finalize):
390
+ break
391
+
392
+
393
+ def _headers_size(headers: dict[str, list[str]]) -> int:
394
+ size = 0
395
+ for name, values in headers.items():
396
+ # 4 is for ": " and "\r\n"
397
+ size += len(name) + 4 + len(values[0])
398
+ return size
399
+
400
+
401
+ def _extract_cookies(headers: list[str]) -> list[harfile.Cookie]:
402
+ return [cookie for items in headers for item in items for cookie in _cookie_to_har(item)]
403
+
404
+
405
+ def _cookie_to_har(cookie: str) -> Iterator[harfile.Cookie]:
406
+ parsed = SimpleCookie(cookie)
407
+ for name, data in parsed.items():
408
+ yield harfile.Cookie(
409
+ name=name,
410
+ value=data.value,
411
+ path=data["path"] or None,
412
+ domain=data["domain"] or None,
413
+ expires=data["expires"] or None,
414
+ httpOnly=data["httponly"] or None,
415
+ secure=data["secure"] or None,
416
+ )
417
+
418
+
281
419
  @dataclass
282
420
  class Replayed:
283
421
  interaction: dict[str, Any]
@@ -222,9 +222,11 @@ class OperationSchemaError(Exception):
222
222
  def from_reference_resolution_error(
223
223
  cls, error: RefResolutionError, path: str | None, method: str | None, full_path: str | None
224
224
  ) -> OperationSchemaError:
225
+ notes = getattr(error, "__notes__", [])
226
+ # Some exceptions don't have the actual reference in them, hence we add it manually via notes
227
+ pointer = f"'{notes[0]}'"
225
228
  message = "Unresolvable JSON pointer in the schema"
226
229
  # Get the pointer value from "Unresolvable JSON pointer: 'components/UnknownParameter'"
227
- pointer = str(error).split(": ", 1)[-1]
228
230
  message += f"\n\nError details:\n JSON pointer: {pointer}"
229
231
  message += "\n This typically means that the schema is referencing a component that doesn't exist."
230
232
  message += f"\n\n{SCHEMA_ERROR_SUGGESTION}"
@@ -77,5 +77,7 @@ class GenerationConfig:
77
77
  allow_x00: bool = True
78
78
  # Generate strings using the given codec
79
79
  codec: str | None = "utf-8"
80
+ # Whether to generate security parameters
81
+ with_security_parameters: bool = True
80
82
  # Header generation configuration
81
83
  headers: HeaderConfig = field(default_factory=HeaderConfig)
schemathesis/models.py CHANGED
@@ -492,6 +492,7 @@ class Case:
492
492
  query=fast_deepcopy(self.query),
493
493
  body=fast_deepcopy(self.body),
494
494
  generation_time=self.generation_time,
495
+ id=self.id,
495
496
  )
496
497
 
497
498
 
@@ -675,9 +676,11 @@ class APIOperation(Generic[P, C]):
675
676
  def get_security_requirements(self) -> list[str]:
676
677
  return self.schema.get_security_requirements(self)
677
678
 
678
- def get_strategies_from_examples(self) -> list[st.SearchStrategy[Case]]:
679
+ def get_strategies_from_examples(
680
+ self, as_strategy_kwargs: dict[str, Any] | None = None
681
+ ) -> list[st.SearchStrategy[Case]]:
679
682
  """Get examples from the API operation."""
680
- return self.schema.get_strategies_from_examples(self)
683
+ return self.schema.get_strategies_from_examples(self, as_strategy_kwargs=as_strategy_kwargs)
681
684
 
682
685
  def get_stateful_tests(self, response: GenericResponse, stateful: Stateful | None) -> Sequence[StatefulTest]:
683
686
  return self.schema.get_stateful_tests(response, self, stateful)
@@ -829,6 +832,7 @@ class Request:
829
832
  method: str
830
833
  uri: str
831
834
  body: str | None
835
+ body_size: int | None
832
836
  headers: Headers
833
837
 
834
838
  @classmethod
@@ -859,6 +863,7 @@ class Request:
859
863
  method=method,
860
864
  headers={key: [value] for (key, value) in prepared.headers.items()},
861
865
  body=serialize_payload(body) if body is not None else body,
866
+ body_size=len(body) if body is not None else None,
862
867
  )
863
868
 
864
869
  def deserialize_body(self) -> bytes | None:
@@ -878,6 +883,7 @@ class Response:
878
883
  message: str
879
884
  headers: dict[str, list[str]]
880
885
  body: str | None
886
+ body_size: int | None
881
887
  encoding: str | None
882
888
  http_version: str
883
889
  elapsed: float
@@ -904,6 +910,7 @@ class Response:
904
910
  status_code=response.status_code,
905
911
  message=response.reason,
906
912
  body=body,
913
+ body_size=len(response.content) if body is not None else None,
907
914
  encoding=response.encoding,
908
915
  headers=headers,
909
916
  http_version=http_version,
@@ -931,6 +938,7 @@ class Response:
931
938
  status_code=response.status_code,
932
939
  message=message,
933
940
  body=body,
941
+ body_size=len(data) if body is not None else None,
934
942
  encoding=encoding,
935
943
  headers=headers,
936
944
  http_version="1.1",
@@ -947,6 +955,9 @@ class Response:
947
955
  return deserialize_payload(self.body)
948
956
 
949
957
 
958
+ TIMEZONE = datetime.datetime.now(datetime.timezone.utc).astimezone().tzinfo
959
+
960
+
950
961
  @dataclass
951
962
  class Interaction:
952
963
  """A single interaction with the target app."""
@@ -956,7 +967,7 @@ class Interaction:
956
967
  checks: list[Check]
957
968
  status: Status
958
969
  data_generation_method: DataGenerationMethod
959
- recorded_at: str = field(default_factory=lambda: datetime.datetime.now().isoformat())
970
+ recorded_at: str = field(default_factory=lambda: datetime.datetime.now(TIMEZONE).isoformat())
960
971
 
961
972
  @classmethod
962
973
  def from_requests(cls, case: Case, response: requests.Response, status: Status, checks: list[Check]) -> Interaction:
@@ -303,6 +303,8 @@ class AfterStatefulExecution(ExecutionEvent):
303
303
 
304
304
  status: Status
305
305
  result: SerializedTestResult
306
+ data_generation_method: list[DataGenerationMethod]
307
+ thread_id: int = field(default_factory=threading.get_ident)
306
308
 
307
309
 
308
310
  @dataclass
@@ -236,12 +236,54 @@ class BaseRunner:
236
236
  state_machine = self.schema.as_state_machine()
237
237
  runner = state_machine.runner(config=config)
238
238
  status = Status.success
239
+
240
+ def from_step_status(step_status: stateful_events.StepStatus) -> Status:
241
+ return {
242
+ stateful_events.StepStatus.SUCCESS: Status.success,
243
+ stateful_events.StepStatus.FAILURE: Status.failure,
244
+ stateful_events.StepStatus.ERROR: Status.error,
245
+ stateful_events.StepStatus.INTERRUPTED: Status.error,
246
+ }[step_status]
247
+
248
+ if self.store_interactions:
249
+ if isinstance(state_machine.schema.transport, RequestsTransport):
250
+
251
+ def on_step_finished(event: stateful_events.StepFinished) -> None:
252
+ if event.response is not None:
253
+ response = cast(requests.Response, event.response)
254
+ result.store_requests_response(
255
+ status=from_step_status(event.status),
256
+ case=event.case,
257
+ response=response,
258
+ checks=event.checks,
259
+ )
260
+
261
+ else:
262
+
263
+ def on_step_finished(event: stateful_events.StepFinished) -> None:
264
+ if event.response is not None:
265
+ response = cast(WSGIResponse, event.response)
266
+ result.store_wsgi_response(
267
+ status=from_step_status(event.status),
268
+ case=event.case,
269
+ response=response,
270
+ headers=self.headers or {},
271
+ elapsed=response.elapsed.total_seconds(),
272
+ checks=event.checks,
273
+ )
274
+ else:
275
+
276
+ def on_step_finished(event: stateful_events.StepFinished) -> None:
277
+ return None
278
+
239
279
  for stateful_event in runner.execute():
240
280
  if isinstance(stateful_event, stateful_events.SuiteFinished):
241
281
  if stateful_event.failures and status != Status.error:
242
282
  status = Status.failure
243
283
  for failure in stateful_event.failures:
244
284
  result.checks.append(failure)
285
+ elif isinstance(stateful_event, stateful_events.StepFinished):
286
+ on_step_finished(stateful_event)
245
287
  elif isinstance(stateful_event, stateful_events.Errored):
246
288
  status = Status.error
247
289
  yield events.StatefulEvent(data=stateful_event)
@@ -249,6 +291,7 @@ class BaseRunner:
249
291
  yield events.AfterStatefulExecution(
250
292
  status=status,
251
293
  result=SerializedTestResult.from_test_result(result),
294
+ data_generation_method=self.schema.data_generation_methods,
252
295
  )
253
296
 
254
297
  def _run_tests(
schemathesis/schemas.py CHANGED
@@ -173,7 +173,9 @@ class BaseSchema(Mapping):
173
173
  ) -> Generator[Result[APIOperation, OperationSchemaError], None, None]:
174
174
  raise NotImplementedError
175
175
 
176
- def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
176
+ def get_strategies_from_examples(
177
+ self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
178
+ ) -> list[SearchStrategy[Case]]:
177
179
  """Get examples from the API operation."""
178
180
  raise NotImplementedError
179
181
 
@@ -171,13 +171,36 @@ def _serialize_stateful_event(event: stateful_events.StatefulEvent) -> dict[str,
171
171
  "timestamp": event.timestamp,
172
172
  "exception": format_exception(event.exception, True),
173
173
  }
174
+ elif isinstance(event, stateful_events.StepFinished):
175
+ data = {
176
+ "timestamp": event.timestamp,
177
+ "status": event.status,
178
+ "transition_id": {
179
+ "name": event.transition_id.name,
180
+ "status_code": event.transition_id.status_code,
181
+ "source": event.transition_id.source,
182
+ }
183
+ if event.transition_id is not None
184
+ else None,
185
+ "target": event.target,
186
+ "response": {
187
+ "status_code": event.response.status_code,
188
+ "elapsed": event.response.elapsed.total_seconds(),
189
+ }
190
+ if event.response is not None
191
+ else None,
192
+ }
174
193
  else:
175
194
  data = asdict(event)
176
195
  return {"data": {event.__class__.__name__: data}}
177
196
 
178
197
 
179
198
  def serialize_after_stateful_execution(event: events.AfterStatefulExecution) -> dict[str, Any] | None:
180
- return {"result": asdict(event.result)}
199
+ return {
200
+ "status": event.status,
201
+ "data_generation_method": event.data_generation_method,
202
+ "result": asdict(event.result),
203
+ }
181
204
 
182
205
 
183
206
  SERIALIZER_MAP = {
@@ -249,7 +249,9 @@ class GraphQLSchema(BaseSchema):
249
249
  **kwargs,
250
250
  )
251
251
 
252
- def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
252
+ def get_strategies_from_examples(
253
+ self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
254
+ ) -> list[SearchStrategy[Case]]:
253
255
  return []
254
256
 
255
257
  def get_stateful_tests(
@@ -42,7 +42,9 @@ class BodyExample:
42
42
  Example = Union[ParameterExample, BodyExample]
43
43
 
44
44
 
45
- def get_strategies_from_examples(operation: APIOperation[OpenAPIParameter, Case]) -> list[SearchStrategy[Case]]:
45
+ def get_strategies_from_examples(
46
+ operation: APIOperation[OpenAPIParameter, Case], as_strategy_kwargs: dict[str, Any] | None = None
47
+ ) -> list[SearchStrategy[Case]]:
46
48
  """Build a set of strategies that generate test cases based on explicit examples in the schema."""
47
49
  maps = {}
48
50
  for location, container in LOCATION_TO_CONTAINER.items():
@@ -65,7 +67,7 @@ def get_strategies_from_examples(operation: APIOperation[OpenAPIParameter, Case]
65
67
  # Add examples from parameter's schemas
66
68
  examples.extend(extract_from_schemas(operation))
67
69
  return [
68
- get_case_strategy(operation=operation, **parameters).map(serialize_components)
70
+ get_case_strategy(operation=operation, **{**parameters, **(as_strategy_kwargs or {})}).map(serialize_components)
69
71
  for parameters in produce_combinations(examples)
70
72
  ]
71
73
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import sys
3
4
  from dataclasses import dataclass
4
5
  from functools import lru_cache
5
6
  from typing import Any, Callable, Dict, Union, overload
@@ -7,6 +8,7 @@ from urllib.request import urlopen
7
8
 
8
9
  import jsonschema
9
10
  import requests
11
+ from jsonschema.exceptions import RefResolutionError
10
12
 
11
13
  from ...constants import DEFAULT_RESPONSE_TIMEOUT
12
14
  from ...internal.copy import fast_deepcopy
@@ -55,6 +57,23 @@ class InliningResolver(jsonschema.RefResolver):
55
57
  )
56
58
  super().__init__(*args, **kwargs)
57
59
 
60
+ if sys.version_info >= (3, 11):
61
+
62
+ def resolve(self, ref: str) -> tuple[str, Any]:
63
+ try:
64
+ return super().resolve(ref)
65
+ except RefResolutionError as exc:
66
+ exc.add_note(ref)
67
+ raise
68
+ else:
69
+
70
+ def resolve(self, ref: str) -> tuple[str, Any]:
71
+ try:
72
+ return super().resolve(ref)
73
+ except RefResolutionError as exc:
74
+ exc.__notes__ = [ref]
75
+ raise
76
+
58
77
  @overload
59
78
  def resolve_all(self, item: dict[str, Any], recursion_level: int = 0) -> dict[str, Any]:
60
79
  pass
@@ -77,7 +77,13 @@ from .parameters import (
77
77
  OpenAPI30Parameter,
78
78
  OpenAPIParameter,
79
79
  )
80
- from .references import RECURSION_DEPTH_LIMIT, UNRESOLVABLE, ConvertingResolver, InliningResolver, resolve_pointer
80
+ from .references import (
81
+ RECURSION_DEPTH_LIMIT,
82
+ UNRESOLVABLE,
83
+ ConvertingResolver,
84
+ InliningResolver,
85
+ resolve_pointer,
86
+ )
81
87
  from .security import BaseSecurityProcessor, OpenAPISecurityProcessor, SwaggerSecurityProcessor
82
88
  from .stateful import create_state_machine
83
89
 
@@ -385,7 +391,8 @@ class BaseOpenAPISchema(BaseSchema):
385
391
  )
386
392
  for parameter in parameters:
387
393
  operation.add_parameter(parameter)
388
- self.security.process_definitions(self.raw_schema, operation, self.resolver)
394
+ if self.generation_config.with_security_parameters:
395
+ self.security.process_definitions(self.raw_schema, operation, self.resolver)
389
396
  self.dispatch_hook("before_init_operation", HookContext(operation=operation), operation)
390
397
  return operation
391
398
 
@@ -399,7 +406,9 @@ class BaseOpenAPISchema(BaseSchema):
399
406
  """Content types available for this API operation."""
400
407
  raise NotImplementedError
401
408
 
402
- def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
409
+ def get_strategies_from_examples(
410
+ self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
411
+ ) -> list[SearchStrategy[Case]]:
403
412
  """Get examples from the API operation."""
404
413
  raise NotImplementedError
405
414
 
@@ -505,12 +514,13 @@ class BaseOpenAPISchema(BaseSchema):
505
514
 
506
515
  def get_parameter_serializer(self, operation: APIOperation, location: str) -> Callable | None:
507
516
  definitions = [item.definition for item in operation.iter_parameters() if item.location == location]
508
- security_parameters = self.security.get_security_definitions_as_parameters(
509
- self.raw_schema, operation, self.resolver, location
510
- )
511
- security_parameters = [item for item in security_parameters if item["in"] == location]
512
- if security_parameters:
513
- definitions.extend(security_parameters)
517
+ if self.generation_config.with_security_parameters:
518
+ security_parameters = self.security.get_security_definitions_as_parameters(
519
+ self.raw_schema, operation, self.resolver, location
520
+ )
521
+ security_parameters = [item for item in security_parameters if item["in"] == location]
522
+ if security_parameters:
523
+ definitions.extend(security_parameters)
514
524
  if definitions:
515
525
  return self._get_parameter_serializer(definitions)
516
526
  return None
@@ -924,9 +934,11 @@ class SwaggerV20(BaseOpenAPISchema):
924
934
  )
925
935
  return collected
926
936
 
927
- def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
937
+ def get_strategies_from_examples(
938
+ self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
939
+ ) -> list[SearchStrategy[Case]]:
928
940
  """Get examples from the API operation."""
929
- return get_strategies_from_examples(operation)
941
+ return get_strategies_from_examples(operation, as_strategy_kwargs=as_strategy_kwargs)
930
942
 
931
943
  def get_response_schema(self, definition: dict[str, Any], scope: str) -> tuple[list[str], dict[str, Any] | None]:
932
944
  scopes, definition = self.resolver.resolve_in_scope(definition, scope)
@@ -1088,9 +1100,11 @@ class OpenApi30(SwaggerV20):
1088
1100
  return scopes, to_json_schema_recursive(option["schema"], self.nullable_name, is_response_schema=True)
1089
1101
  return scopes, None
1090
1102
 
1091
- def get_strategies_from_examples(self, operation: APIOperation) -> list[SearchStrategy[Case]]:
1103
+ def get_strategies_from_examples(
1104
+ self, operation: APIOperation, as_strategy_kwargs: dict[str, Any] | None = None
1105
+ ) -> list[SearchStrategy[Case]]:
1092
1106
  """Get examples from the API operation."""
1093
- return get_strategies_from_examples(operation)
1107
+ return get_strategies_from_examples(operation, as_strategy_kwargs=as_strategy_kwargs)
1094
1108
 
1095
1109
  def get_content_types(self, operation: APIOperation, response: GenericResponse) -> list[str]:
1096
1110
  definitions = self._get_response_definitions(operation, response)
@@ -126,10 +126,23 @@ class OpenAPISecurityProcessor(BaseSecurityProcessor):
126
126
  """In Open API 3 security definitions are located in ``components`` and may have references inside."""
127
127
  components = schema.get("components", {})
128
128
  security_schemes = components.get("securitySchemes", {})
129
+ # At this point, the resolution scope could differ from the root scope, that's why we need to restore it
130
+ # as now we resolve root-level references
131
+ if len(resolver._scopes_stack) > 1:
132
+ scope = resolver.resolution_scope
133
+ resolver.pop_scope()
134
+ else:
135
+ scope = None
129
136
  resolve = resolver.resolve
130
- if "$ref" in security_schemes:
131
- return resolve(security_schemes["$ref"])[1]
132
- return {key: resolve(value["$ref"])[1] if "$ref" in value else value for key, value in security_schemes.items()}
137
+ try:
138
+ if "$ref" in security_schemes:
139
+ return resolve(security_schemes["$ref"])[1]
140
+ return {
141
+ key: resolve(value["$ref"])[1] if "$ref" in value else value for key, value in security_schemes.items()
142
+ }
143
+ finally:
144
+ if scope is not None:
145
+ resolver._scopes_stack.append(scope)
133
146
 
134
147
  def _make_http_auth_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
135
148
  schema = make_auth_header_schema(definition)
@@ -39,6 +39,8 @@ class RunnerContext:
39
39
  seen_in_suite: set[FailureKey] = field(default_factory=set)
40
40
  # Unique failures collected in the current suite
41
41
  failures_for_suite: list[Check] = field(default_factory=list)
42
+ # All checks executed in the current run
43
+ checks_for_step: list[Check] = field(default_factory=list)
42
44
  # Status of the current step
43
45
  current_step_status: events.StepStatus | None = None
44
46
  current_response: GenericResponse | None = None
@@ -55,10 +57,13 @@ class RunnerContext:
55
57
  return events.ScenarioStatus.INTERRUPTED
56
58
  return events.ScenarioStatus.REJECTED
57
59
 
58
- def reset_step(self) -> None:
60
+ def reset_scenario(self) -> None:
59
61
  self.current_step_status = None
60
62
  self.current_response = None
61
63
 
64
+ def reset_step(self) -> None:
65
+ self.checks_for_step = []
66
+
62
67
  def step_succeeded(self) -> None:
63
68
  self.current_step_status = events.StepStatus.SUCCESS
64
69
 
@@ -100,4 +105,4 @@ class RunnerContext:
100
105
  def reset(self) -> None:
101
106
  self.failures_for_suite = []
102
107
  self.seen_in_suite.clear()
103
- self.reset_step()
108
+ self.reset_scenario()
@@ -6,7 +6,8 @@ from enum import Enum
6
6
  from typing import TYPE_CHECKING, Type
7
7
 
8
8
  if TYPE_CHECKING:
9
- from ..models import Check
9
+ from ..models import Case, Check
10
+ from ..transports.responses import GenericResponse
10
11
  from .state_machine import APIStateMachine
11
12
 
12
13
 
@@ -178,17 +179,28 @@ class StepFinished(StatefulEvent):
178
179
  status: StepStatus
179
180
  transition_id: TransitionId | None
180
181
  target: str
181
- response: ResponseData | None
182
+ case: Case
183
+ response: GenericResponse | None
184
+ checks: list[Check]
182
185
 
183
- __slots__ = ("timestamp", "status", "transition_id", "target", "response")
186
+ __slots__ = ("timestamp", "status", "transition_id", "target", "case", "response", "checks")
184
187
 
185
188
  def __init__(
186
- self, *, status: StepStatus, transition_id: TransitionId | None, target: str, response: ResponseData | None
189
+ self,
190
+ *,
191
+ status: StepStatus,
192
+ transition_id: TransitionId | None,
193
+ target: str,
194
+ case: Case,
195
+ response: GenericResponse | None,
196
+ checks: list[Check],
187
197
  ) -> None:
188
198
  self.status = status
189
199
  self.transition_id = transition_id
190
200
  self.target = target
201
+ self.case = case
191
202
  self.response = response
203
+ self.checks = checks
192
204
  self.timestamp = time.monotonic()
193
205
 
194
206
 
@@ -157,30 +157,25 @@ def _execute_state_machine_loop(
157
157
  )
158
158
  else:
159
159
  transition_id = None
160
- response: events.ResponseData | None
161
- if ctx.current_response is not None:
162
- response = events.ResponseData(
163
- status_code=ctx.current_response.status_code,
164
- elapsed=ctx.current_response.elapsed.total_seconds(),
165
- )
166
- else:
167
- response = None
168
160
  status = cast(events.StepStatus, ctx.current_step_status)
169
161
  event_queue.put(
170
162
  events.StepFinished(
171
163
  status=status,
172
164
  transition_id=transition_id,
173
165
  target=case.operation.verbose_name,
174
- response=response,
166
+ case=case,
167
+ response=ctx.current_response,
168
+ checks=ctx.checks_for_step,
175
169
  )
176
170
  )
171
+ ctx.reset_step()
177
172
  return result
178
173
 
179
174
  def validate_response(
180
175
  self, response: GenericResponse, case: Case, additional_checks: tuple[CheckFunction, ...] = ()
181
176
  ) -> None:
182
177
  ctx.current_response = response
183
- validate_response(response, case, ctx, config.checks, additional_checks)
178
+ validate_response(response, case, ctx, config.checks, ctx.checks_for_step, additional_checks)
184
179
 
185
180
  def teardown(self) -> None:
186
181
  build_ctx = current_build_context()
@@ -190,7 +185,7 @@ def _execute_state_machine_loop(
190
185
  is_final=build_ctx.is_final,
191
186
  )
192
187
  )
193
- ctx.reset_step()
188
+ ctx.reset_scenario()
194
189
  super().teardown()
195
190
 
196
191
  while True:
@@ -51,7 +51,7 @@ class StateMachineSink:
51
51
  responses = self.response_times.setdefault(event.target, {})
52
52
  if event.response is not None:
53
53
  average = responses.setdefault(event.response.status_code, AverageResponseTime())
54
- average.total += event.response.elapsed
54
+ average.total += event.response.elapsed.total_seconds()
55
55
  average.count += 1
56
56
  elif isinstance(event, events.ScenarioFinished):
57
57
  self.scenarios[event.status] += 1
@@ -7,7 +7,7 @@ from .context import RunnerContext
7
7
 
8
8
  if TYPE_CHECKING:
9
9
  from ..failures import FailureContext
10
- from ..models import Case, CheckFunction
10
+ from ..models import Case, CheckFunction, Check
11
11
  from ..transports.responses import GenericResponse
12
12
 
13
13
 
@@ -16,6 +16,7 @@ def validate_response(
16
16
  case: Case,
17
17
  failures: RunnerContext,
18
18
  checks: tuple[CheckFunction, ...],
19
+ check_results: list[Check],
19
20
  additional_checks: tuple[CheckFunction, ...] = (),
20
21
  ) -> None:
21
22
  """Validate the response against the provided checks."""
@@ -28,18 +29,18 @@ def validate_response(
28
29
  exceptions.append(exc)
29
30
  if failures.is_seen_in_suite(exc):
30
31
  return
31
- failures.add_failed_check(
32
- Check(
33
- name=name,
34
- value=Status.failure,
35
- response=response,
36
- elapsed=response.elapsed.total_seconds(),
37
- example=copied_case,
38
- message=message,
39
- context=context,
40
- request=None,
41
- )
32
+ failed_check = Check(
33
+ name=name,
34
+ value=Status.failure,
35
+ response=response,
36
+ elapsed=response.elapsed.total_seconds(),
37
+ example=copied_case,
38
+ message=message,
39
+ context=context,
40
+ request=None,
42
41
  )
42
+ failures.add_failed_check(failed_check)
43
+ check_results.append(failed_check)
43
44
  failures.mark_as_seen_in_suite(exc)
44
45
 
45
46
  for check in checks + additional_checks:
@@ -47,6 +48,17 @@ def validate_response(
47
48
  copied_case = case.partial_deepcopy()
48
49
  try:
49
50
  check(response, copied_case)
51
+ skip_check = check(response, copied_case)
52
+ if not skip_check:
53
+ passed_check = Check(
54
+ name=name,
55
+ value=Status.success,
56
+ response=response,
57
+ elapsed=response.elapsed.total_seconds(),
58
+ example=copied_case,
59
+ request=None,
60
+ )
61
+ check_results.append(passed_check)
50
62
  except CheckFailed as exc:
51
63
  if failures.is_seen_in_run(exc):
52
64
  continue
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: schemathesis
3
- Version: 3.30.3
3
+ Version: 3.31.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
@@ -31,6 +31,7 @@ Requires-Python: >=3.8
31
31
  Requires-Dist: backoff<3.0,>=2.1.2
32
32
  Requires-Dist: click<9.0,>=7.0
33
33
  Requires-Dist: colorama<1.0,>=0.4
34
+ Requires-Dist: harfile<1.0,>=0.3.0
34
35
  Requires-Dist: httpx<1.0,>=0.22.0
35
36
  Requires-Dist: hypothesis-graphql<1,>=0.11.0
36
37
  Requires-Dist: hypothesis-jsonschema<0.24,>=0.23.1
@@ -1,7 +1,7 @@
1
1
  schemathesis/__init__.py,sha256=pNaTfaC3NSdediNQuH9QAcuIx3U-MmSydvhS65FZrxw,1984
2
2
  schemathesis/_compat.py,sha256=y4RZd59i2NCnZ91VQhnKeMn_8t3SgvLOk2Xm8nymUHY,1837
3
3
  schemathesis/_dependency_versions.py,sha256=pjEkkGAfOQJYNb-9UOo84V8nj_lKHr_TGDVdFwY2UU0,816
4
- schemathesis/_hypothesis.py,sha256=GeH2umc3HiK93JZ9tOKALp2nb3mbpaLFkq4SbCY0_TM,10998
4
+ schemathesis/_hypothesis.py,sha256=Igb1x3am3tafVhZ6ikY7yrmQdNthC0PZFEYVCdNAA3w,11207
5
5
  schemathesis/_lazy_import.py,sha256=aMhWYgbU2JOltyWBb32vnWBb6kykOghucEzI_F70yVE,470
6
6
  schemathesis/_override.py,sha256=3CbA7P9Q89W3ymaYxiOV5Xpv1yhoBqroLK4YRpYMjX4,1630
7
7
  schemathesis/_rate_limiter.py,sha256=q_XWst5hzuAyXQRiZc4s_bx7-JlPYZM_yKDmeavt3oo,242
@@ -10,27 +10,27 @@ schemathesis/auths.py,sha256=NeJqsjtDgJtHMyrHc6V1NTpkAh1K8ZKLalpB3v80cT4,14734
10
10
  schemathesis/checks.py,sha256=XplKduiRbRrxZx-wDMGaW91aIitr5RL9vjMP6bvCFRg,2272
11
11
  schemathesis/code_samples.py,sha256=xk1-1jnXg5hS40VzIZp8PEtZwGaazNlVKMT7_X-zG-M,4123
12
12
  schemathesis/constants.py,sha256=l1YQ7PXhEj9dyf9CTESVUpPOaFCH7iz-Fe8o4v6Th_s,2673
13
- schemathesis/exceptions.py,sha256=ZX_KqnkQ80GACsBgm6lJV43aFBJws-2vGtnFu72n-O4,19576
13
+ schemathesis/exceptions.py,sha256=tFDlNui1Sxc8a6-OQCIjj62SoToYoEN9zveguPpzQIc,19712
14
14
  schemathesis/failures.py,sha256=wXz5Kr5i-ojcYc-BdzFlNbNGOfoVXHZM6kd4iULdHK4,7003
15
15
  schemathesis/filters.py,sha256=0fYzn9sJ35k3Znx1P8FrbSdoUcdslcibtGh-IOTRwB8,10251
16
16
  schemathesis/graphql.py,sha256=YkoKWY5K8lxp7H3ikAs-IsoDbiPwJvChG7O8p3DgwtI,229
17
17
  schemathesis/hooks.py,sha256=dveqMmThIvt4fDahUXhU2nCq5pFvYjzzd1Ys_MhrJZA,12398
18
18
  schemathesis/lazy.py,sha256=eVdGkTZK0fWvUlFUCFGGlViH2NWEtYIjxiNkF4fBWhI,15218
19
19
  schemathesis/loaders.py,sha256=OtCD1o0TVmSNAUF7dgHpouoAXtY6w9vEtsRVGv4lE0g,4588
20
- schemathesis/models.py,sha256=byTGJD6zyXFvSvQCe-Mxu3lOdvU3EQ9vmadv6Jv6f5I,44025
20
+ schemathesis/models.py,sha256=4kAiutx5BEZ4h4AHMvZVW7gJpObur7kkGFuuF2cTEkU,44491
21
21
  schemathesis/parameters.py,sha256=PndmqQRlEYsCt1kWjSShPsFf6vj7X_7FRdz_-A95eNg,2258
22
22
  schemathesis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
23
  schemathesis/sanitization.py,sha256=mRR4YvXpzqbmgX8Xu6rume6LBcz9g_oyusvbesZl44I,8958
24
- schemathesis/schemas.py,sha256=aa84Kmin4lu68vU4y3DpPGo4Z1Pt7LFCqe9kzW35Asc,18267
24
+ schemathesis/schemas.py,sha256=nUJIfR0mXYpKwEX3mZiS3hpUDdkD3vulROVeovL0dqU,18331
25
25
  schemathesis/serializers.py,sha256=kxXZ-UGa1v_vOm0sC4QYcrNv4rfvI7tHGT2elRVbCbc,11649
26
26
  schemathesis/targets.py,sha256=N1Zzgqa1PNycWIeGpra7q-6ASn2x4r9Jompn35bmlsE,1163
27
27
  schemathesis/throttling.py,sha256=aisUc4MJDGIOGUAs9L2DlWWpdd4KyAFuNVKhYoaUC9M,1719
28
28
  schemathesis/types.py,sha256=xOzNAeMs6qqeaJnWs5Fpw5JPbvVjyfRfxTJa3G2Ln5I,920
29
29
  schemathesis/utils.py,sha256=NX04p9mO-lCAH3DIISXDXPxWZk6lkGNM4-ubRi8vlvY,5234
30
- schemathesis/cli/__init__.py,sha256=EmgM2nTU81Q9BdAxwFl6hBsGZl1SYTr9tvCqHDyWu2U,65680
30
+ schemathesis/cli/__init__.py,sha256=Gw2uVput5JySsSg8gjAzibi-tK_PixwyNYyQLTukAJE,66535
31
31
  schemathesis/cli/__main__.py,sha256=MWaenjaUTZIfNPFzKmnkTiawUri7DVldtg3mirLwzU8,92
32
- schemathesis/cli/callbacks.py,sha256=tZSe6EIyc1fdKZ8xGaE0w0LuGCMtT7m9cW41Rd0Nah0,14949
33
- schemathesis/cli/cassettes.py,sha256=WLt2svZm23vZX5buQWmWO94QwQEC8OM2g8yH50rfatE,12990
32
+ schemathesis/cli/callbacks.py,sha256=R2noVRu8zDBWmA3dJ2YnmhAjYdkxPDA1zIpA3_2DkFQ,15144
33
+ schemathesis/cli/cassettes.py,sha256=3vumBEP0XXBVpNvwMEGYvcIp1TSHtCNUIL60TPDVgD4,19024
34
34
  schemathesis/cli/constants.py,sha256=ogSuZs68KvzHNKF0yaiBkxLcGbo8IVR3xaIfsy1H1IQ,1546
35
35
  schemathesis/cli/context.py,sha256=EMeyAbU9mRujR46anc43yr6ab4rGYtIDaHC3cV9Qa-Q,2092
36
36
  schemathesis/cli/debug.py,sha256=_YA-bX1ujHl4bqQDEum7M-I2XHBTEGbvgkhvcvKhmgU,658
@@ -57,7 +57,7 @@ schemathesis/extra/pytest_plugin.py,sha256=ymicV2NjmSzee0ccUUUjNEvb9ihCxxf_8M60g
57
57
  schemathesis/fixups/__init__.py,sha256=RP5QYJVJhp8LXjhH89fCRaIVU26dHCy74jD9seoYMuc,967
58
58
  schemathesis/fixups/fast_api.py,sha256=mn-KzBqnR8jl4W5fY-_ZySabMDMUnpzCIESMHnlvE1c,1304
59
59
  schemathesis/fixups/utf8_bom.py,sha256=lWT9RNmJG8i-l5AXIpaCT3qCPUwRgzXPW3eoOjmZETA,745
60
- schemathesis/generation/__init__.py,sha256=sK7wYwW0s5fD4AtLft-_DWIlIqRDVb_cBxQK7tVYTi4,2278
60
+ schemathesis/generation/__init__.py,sha256=mC1NVAHyce1_B_wu-GYO9U21Gut8KFrjPXETREC9ABQ,2366
61
61
  schemathesis/internal/__init__.py,sha256=93HcdG3LF0BbQKbCteOsFMa1w6nXl8yTmx87QLNJOik,161
62
62
  schemathesis/internal/copy.py,sha256=lcK01CODz6jogXyH0tsKkPv1PBEX8jeBPxI9MzQ6LN4,942
63
63
  schemathesis/internal/datetime.py,sha256=zPLBL0XXLNfP-KYel3H2m8pnsxjsA_4d-zTOhJg2EPQ,136
@@ -68,11 +68,11 @@ schemathesis/internal/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo
68
68
  schemathesis/internal/transformation.py,sha256=3S6AzAqdsEsB5iobFgSuvL0UMUqH0JHC7hGxKwcpqPw,450
69
69
  schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QAtMPyEw,966
70
70
  schemathesis/runner/__init__.py,sha256=n-4CanmlfMiGnir6xbZH-fM5a0pe2GloBFj2NTtet-M,21419
71
- schemathesis/runner/events.py,sha256=PJceb_jC-2BKEdMIrH8mSCtt8lbPPq6Gs5qjPicwxmU,10575
71
+ schemathesis/runner/events.py,sha256=VVFy-qAgxoHBNrexBakq_QX_hx52pnWK-67HgSPpYRw,10694
72
72
  schemathesis/runner/probes.py,sha256=J-TT0hOKu9j4htWKBcYKmsomcRxmvOl4WpmnKLVXu8M,5546
73
73
  schemathesis/runner/serialization.py,sha256=J8fuG8MSJq3rE3IJs73U1YXWFrNa05k7PGd5Bvq1uec,17356
74
74
  schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
75
- schemathesis/runner/impl/core.py,sha256=u56WvKOJY96ShHj5MoAcwys_th3DAxLAAoipx7wvWm8,42034
75
+ schemathesis/runner/impl/core.py,sha256=Ec_HlCRJdQSluN_JYEt0eh99SFXf0RGX6knPpayPUXw,44148
76
76
  schemathesis/runner/impl/solo.py,sha256=MatxThgqKsY2tX_hVwjy78oKFeKejb6dFJoX3kGzW4U,3359
77
77
  schemathesis/runner/impl/threadpool.py,sha256=fj2QYoWxIJIxpTCcJQyM_VCRO1YDnW9XQJJnNVFVQxY,15253
78
78
  schemathesis/service/__init__.py,sha256=cDVTCFD1G-vvhxZkJUwiToTAEQ-0ByIoqwXvJBCf_V8,472
@@ -86,7 +86,7 @@ schemathesis/service/hosts.py,sha256=ad2Lxq9Zcc9PP-1eFLQnxen4ImglcGOH8n7CGG72NNg
86
86
  schemathesis/service/metadata.py,sha256=x2LeCED1mdPf-YQJmjY8xtcIKHfD1ap5V0BGl-UgqNo,2087
87
87
  schemathesis/service/models.py,sha256=ihItUJ9CvH4TvmdfJY3W88NR82OODF8a3RD7WRXn6RM,6578
88
88
  schemathesis/service/report.py,sha256=4A8nf6_KOjDW3x1VXF8gSf_WY2xXp1Cbz-Owl_GeR7o,8294
89
- schemathesis/service/serialization.py,sha256=LbEUhcDI9hsFDrxjjH-79QhRd71pKHHc9odsVsUuC7s,9776
89
+ schemathesis/service/serialization.py,sha256=GFNc1-w1ShZopFdR7OFLT9ZKe8Y2S1hYnlXMrdMj3VE,10600
90
90
  schemathesis/service/usage.py,sha256=UbXqxeDq5mAjKkfV4hApZsReZmQHXiqoXUYn_Z6YuZk,2438
91
91
  schemathesis/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
92
92
  schemathesis/specs/graphql/__init__.py,sha256=fgyHtvWNUVWismBTOqxQtgLoTighTfvMv6v6QCD_Oyc,85
@@ -94,7 +94,7 @@ schemathesis/specs/graphql/_cache.py,sha256=7ras3q_InDJBPykgHroerl9f2jFamC8xJD35
94
94
  schemathesis/specs/graphql/loaders.py,sha256=qxNGL67_AfhoRh0hIxlnJVe6do26vqwWS_TrJtB-Lro,12198
95
95
  schemathesis/specs/graphql/nodes.py,sha256=bE3G1kNmqJ8OV4igBvIK-UORrkQA6Nofduf87O3TD9I,541
96
96
  schemathesis/specs/graphql/scalars.py,sha256=9tvLTiYVe8A_E8ASA0czz3Z0Mp9lyak7R4wHpAE_jKo,1805
97
- schemathesis/specs/graphql/schemas.py,sha256=ICIejnv05Q9uxljQU06i9RSVa10mAdTESBfM17wMkaI,13491
97
+ schemathesis/specs/graphql/schemas.py,sha256=i6fAW9pYcOplQE7BejP6P8GQ9z6Y43Vx4_feVvbd1B4,13555
98
98
  schemathesis/specs/graphql/validation.py,sha256=uINIOt-2E7ZuQV2CxKzwez-7L9tDtqzMSpnVoRWvxy0,1635
99
99
  schemathesis/specs/openapi/__init__.py,sha256=HDcx3bqpa6qWPpyMrxAbM3uTo0Lqpg-BUNZhDJSJKnw,279
100
100
  schemathesis/specs/openapi/_cache.py,sha256=PAiAu4X_a2PQgD2lG5H3iisXdyg4SaHpU46bRZvfNkM,4320
@@ -103,16 +103,16 @@ schemathesis/specs/openapi/checks.py,sha256=1Fu3Kgai9ySCoGtCrx99Q9oVCEWXgkqHd1gT
103
103
  schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnKDwCwPLhID4,783
104
104
  schemathesis/specs/openapi/converter.py,sha256=TaYgc5BBHPdkN-n0lqpbeVgLu3eL3L8Wu3y_Vo3TJaQ,2800
105
105
  schemathesis/specs/openapi/definitions.py,sha256=Z186F0gNBSCmPg-Kk7Q-n6XxEZHIOzgUyeqixlC62XE,94058
106
- schemathesis/specs/openapi/examples.py,sha256=OxzcYWlNQzAjkNf8IAGxEGnQ4DelY4mtPwDLodWSRAE,15038
106
+ schemathesis/specs/openapi/examples.py,sha256=5bjmW3BnJVTiLlWZbimdfOzQQFR6m1P9G0FErr9g3WI,15128
107
107
  schemathesis/specs/openapi/filters.py,sha256=6Q9eNQ6zCR-NQkUxgnkSDWxfk3hsZuxemBv7v1rhwb4,1437
108
108
  schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
109
109
  schemathesis/specs/openapi/links.py,sha256=2ucOLs50OhCqu0PEdbT_BGUM3fKnHBl97YGISLpAxLY,16023
110
110
  schemathesis/specs/openapi/loaders.py,sha256=JJdIz1aT03J9WmUWTLOz6Yhuu69IqmhobQ9_vL6XJ6U,24916
111
111
  schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
112
112
  schemathesis/specs/openapi/parameters.py,sha256=_6vNCnPXcdxjfAQbykCRLHjvmTpu_02xDJghxDrGYr8,13611
113
- schemathesis/specs/openapi/references.py,sha256=i1Jd8EtEpSpauFW29DQsYM-JvB4jtYI7dpJAJcdjYe0,9276
114
- schemathesis/specs/openapi/schemas.py,sha256=rEsvuRM4mFbUEfaNfANadyfWhA-iaXbaBMZpjq1anBk,51761
115
- schemathesis/specs/openapi/security.py,sha256=sTOCYEsJmxGY2dglrlZRYmJzNZjEZAHm5bZtbYFHLmI,6612
113
+ schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
114
+ schemathesis/specs/openapi/schemas.py,sha256=UMPfQKndW7HRAeLNlu0zmZVmJMaD7CvRJ8p-c8a93uc,52204
115
+ schemathesis/specs/openapi/security.py,sha256=nEhDB_SvEFldmfpa9uOQywfWN6DtXHKmgtwucJvfN5Q,7096
116
116
  schemathesis/specs/openapi/serialization.py,sha256=5qGdFHZ3n80UlbSXrO_bkr4Al_7ci_Z3aSUjZczNDQY,11384
117
117
  schemathesis/specs/openapi/utils.py,sha256=-TCu0hTrlwp2x5qHNp-TxiHRMeIZC9OBmlhLssjRIiQ,742
118
118
  schemathesis/specs/openapi/validation.py,sha256=Q9ThZlwU-mSz7ExDnIivnZGi1ivC5hlX2mIMRAM79kc,999
@@ -132,20 +132,20 @@ schemathesis/specs/openapi/stateful/statistic.py,sha256=EJK4NqeAYRYl1FtU9YEuTLyh
132
132
  schemathesis/specs/openapi/stateful/types.py,sha256=UuGcCTFvaHsqeLN9ZeUNcbjsEwmthoT3UcHfDHchOYo,419
133
133
  schemathesis/stateful/__init__.py,sha256=qyQJ-9Ect-AWZiAsK63F3BTGu-jZnPCOp1q46YAonkQ,4911
134
134
  schemathesis/stateful/config.py,sha256=kuKGLNNk0YrR6G0IzcZh6v0oysHHTPfHi9nHUsfQsF0,2298
135
- schemathesis/stateful/context.py,sha256=P2kVeMOMsHOpQ0wyeZ2OfTU5fSCgehKJxzai1_tXiwc,3747
136
- schemathesis/stateful/events.py,sha256=wXh4O9FELmNWAUnupAFpUFOokjesvQOE8joBUAqjbrU,4947
137
- schemathesis/stateful/runner.py,sha256=ZcnU0lERjenpG0XnQUSeHH0MOesCg7K97fE53eIxvvc,9633
138
- schemathesis/stateful/sink.py,sha256=V1ReRTpN3VfAXtcnSEke4UHhu7IxrYQs0s_gSDMaqWw,2425
135
+ schemathesis/stateful/context.py,sha256=nYurdKz3yLA4xvOAUBc5hiJikDaMTiIn69RQhskreVU,3932
136
+ schemathesis/stateful/events.py,sha256=VF0jRi4eq5ybQnyIjSvScRMjQK-NwKd-pNZ7ZVJwygk,5215
137
+ schemathesis/stateful/runner.py,sha256=4VDjo_PgSoNxfMVsAOfXlJVXZPhqh8fc76Lt7cXPXZ4,9404
138
+ schemathesis/stateful/sink.py,sha256=yWY9xJeUyOWOsu1tNxCgsDFQVJxK2xgQavJ9vQoxK1I,2441
139
139
  schemathesis/stateful/state_machine.py,sha256=H-AzMPTKuCKnoCv0b7XPFDsHkzRftNfbvh5xb2H5Hfk,12156
140
140
  schemathesis/stateful/statistic.py,sha256=xPLiCw61ofNXQicqcK_sZyLHiqiGcgQARpwd8AiRubM,487
141
- schemathesis/stateful/validation.py,sha256=tqUtd9lEq4ZbJf1vA8hRxj_tO6l9B1DM-SRVVB6qKd8,2324
141
+ schemathesis/stateful/validation.py,sha256=RiXnZBTu6zMnjCpZBLyMwPDJrPTnSHWSijrg34VtRoM,2825
142
142
  schemathesis/transports/__init__.py,sha256=fwICPJBLHA7_L4IDAiW1SmxNHXlLD_Eqp-74w9TRRIs,12160
143
143
  schemathesis/transports/auth.py,sha256=4z7c-K7lfyyVqgR6X1v4yiE8ewR_ViAznWFTAsCL0RI,405
144
144
  schemathesis/transports/content_types.py,sha256=VrcRQvF5T_TUjrCyrZcYF2LOwKfs3IrLcMtkVSp1ImI,2189
145
145
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
146
146
  schemathesis/transports/responses.py,sha256=6-gvVcRK0Ho_lSydUysBNFWoJwZEiEgf6Iv-GWkQGd8,1675
147
- schemathesis-3.30.3.dist-info/METADATA,sha256=EJGm-5rC8P8sk_GgBDOfehfb5MdK4a9O6pU7jISsbTU,17671
148
- schemathesis-3.30.3.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
149
- schemathesis-3.30.3.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
150
- schemathesis-3.30.3.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
151
- schemathesis-3.30.3.dist-info/RECORD,,
147
+ schemathesis-3.31.0.dist-info/METADATA,sha256=xq28YKL2SXo0XpmPf68tqEGoY8khEIjTEnoEPkV4tPo,17706
148
+ schemathesis-3.31.0.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
149
+ schemathesis-3.31.0.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
150
+ schemathesis-3.31.0.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
151
+ schemathesis-3.31.0.dist-info/RECORD,,