schemathesis 3.35.4__py3-none-any.whl → 3.36.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.
Files changed (85) hide show
  1. schemathesis/__init__.py +5 -5
  2. schemathesis/_hypothesis.py +12 -6
  3. schemathesis/_override.py +4 -4
  4. schemathesis/auths.py +1 -1
  5. schemathesis/checks.py +8 -5
  6. schemathesis/cli/__init__.py +23 -26
  7. schemathesis/cli/callbacks.py +6 -4
  8. schemathesis/cli/cassettes.py +67 -41
  9. schemathesis/cli/context.py +7 -6
  10. schemathesis/cli/junitxml.py +1 -1
  11. schemathesis/cli/options.py +7 -4
  12. schemathesis/cli/output/default.py +5 -5
  13. schemathesis/cli/reporting.py +4 -2
  14. schemathesis/code_samples.py +4 -3
  15. schemathesis/contrib/unique_data.py +1 -2
  16. schemathesis/exceptions.py +4 -3
  17. schemathesis/extra/_flask.py +4 -1
  18. schemathesis/extra/pytest_plugin.py +6 -3
  19. schemathesis/failures.py +2 -1
  20. schemathesis/filters.py +2 -2
  21. schemathesis/generation/__init__.py +2 -2
  22. schemathesis/generation/_hypothesis.py +1 -1
  23. schemathesis/generation/coverage.py +53 -12
  24. schemathesis/graphql.py +0 -1
  25. schemathesis/hooks.py +3 -3
  26. schemathesis/internal/checks.py +53 -0
  27. schemathesis/lazy.py +10 -7
  28. schemathesis/loaders.py +3 -3
  29. schemathesis/models.py +59 -23
  30. schemathesis/runner/__init__.py +12 -6
  31. schemathesis/runner/events.py +1 -1
  32. schemathesis/runner/impl/context.py +72 -0
  33. schemathesis/runner/impl/core.py +105 -67
  34. schemathesis/runner/impl/solo.py +17 -20
  35. schemathesis/runner/impl/threadpool.py +65 -72
  36. schemathesis/runner/serialization.py +4 -3
  37. schemathesis/sanitization.py +2 -1
  38. schemathesis/schemas.py +20 -22
  39. schemathesis/serializers.py +2 -0
  40. schemathesis/service/client.py +1 -1
  41. schemathesis/service/events.py +4 -1
  42. schemathesis/service/extensions.py +2 -2
  43. schemathesis/service/hosts.py +4 -2
  44. schemathesis/service/models.py +3 -3
  45. schemathesis/service/report.py +3 -3
  46. schemathesis/service/serialization.py +4 -2
  47. schemathesis/specs/graphql/loaders.py +5 -4
  48. schemathesis/specs/graphql/schemas.py +13 -8
  49. schemathesis/specs/openapi/checks.py +76 -27
  50. schemathesis/specs/openapi/definitions.py +1 -5
  51. schemathesis/specs/openapi/examples.py +92 -2
  52. schemathesis/specs/openapi/expressions/__init__.py +7 -0
  53. schemathesis/specs/openapi/expressions/extractors.py +4 -1
  54. schemathesis/specs/openapi/expressions/nodes.py +5 -3
  55. schemathesis/specs/openapi/links.py +4 -4
  56. schemathesis/specs/openapi/loaders.py +6 -5
  57. schemathesis/specs/openapi/negative/__init__.py +5 -3
  58. schemathesis/specs/openapi/negative/mutations.py +5 -4
  59. schemathesis/specs/openapi/parameters.py +4 -2
  60. schemathesis/specs/openapi/schemas.py +28 -13
  61. schemathesis/specs/openapi/security.py +6 -4
  62. schemathesis/specs/openapi/stateful/__init__.py +2 -2
  63. schemathesis/specs/openapi/stateful/statistic.py +3 -3
  64. schemathesis/specs/openapi/stateful/types.py +3 -2
  65. schemathesis/stateful/__init__.py +3 -3
  66. schemathesis/stateful/config.py +2 -1
  67. schemathesis/stateful/context.py +13 -3
  68. schemathesis/stateful/events.py +3 -3
  69. schemathesis/stateful/runner.py +24 -6
  70. schemathesis/stateful/sink.py +1 -1
  71. schemathesis/stateful/state_machine.py +7 -6
  72. schemathesis/stateful/statistic.py +3 -1
  73. schemathesis/stateful/validation.py +10 -5
  74. schemathesis/transports/__init__.py +2 -2
  75. schemathesis/transports/asgi.py +7 -0
  76. schemathesis/transports/auth.py +2 -1
  77. schemathesis/transports/content_types.py +1 -1
  78. schemathesis/transports/responses.py +2 -1
  79. schemathesis/utils.py +4 -2
  80. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/METADATA +1 -1
  81. schemathesis-3.36.0.dist-info/RECORD +157 -0
  82. schemathesis-3.35.4.dist-info/RECORD +0 -154
  83. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/WHEEL +0 -0
  84. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/entry_points.txt +0 -0
  85. {schemathesis-3.35.4.dist-info → schemathesis-3.36.0.dist-info}/licenses/LICENSE +0 -0
@@ -6,7 +6,6 @@ import shutil
6
6
  import textwrap
7
7
  import time
8
8
  from importlib import metadata
9
- from queue import Queue
10
9
  from types import GeneratorType
11
10
  from typing import TYPE_CHECKING, Any, Generator, Literal, cast
12
11
 
@@ -44,6 +43,8 @@ from ..handlers import EventHandler
44
43
  from ..reporting import TEST_CASE_ID_TITLE, get_runtime_error_suggestion, group_by_case, split_traceback
45
44
 
46
45
  if TYPE_CHECKING:
46
+ from queue import Queue
47
+
47
48
  import requests
48
49
 
49
50
  SPINNER_REPETITION_NUMBER = 10
@@ -372,7 +373,7 @@ def display_analysis(context: ExecutionContext) -> None:
372
373
  click.echo()
373
374
  if isinstance(analysis, AnalysisSuccess):
374
375
  click.secho(analysis.message, bold=True)
375
- click.echo("\nAnalysis took: {:.2f}ms".format(analysis.elapsed))
376
+ click.echo(f"\nAnalysis took: {analysis.elapsed:.2f}ms")
376
377
  if analysis.extensions:
377
378
  known = []
378
379
  failed = []
@@ -417,8 +418,8 @@ def display_analysis(context: ExecutionContext) -> None:
417
418
  click.secho("Error\n", fg="red", bold=True)
418
419
  _display_service_network_error(response)
419
420
  click.echo()
420
- return None
421
- elif isinstance(exception, requests.RequestException):
421
+ return
422
+ if isinstance(exception, requests.RequestException):
422
423
  message, extras = extract_requests_exception_details(exception)
423
424
  suggestion = "Please check your network connection and try again."
424
425
  title = "Network Error"
@@ -649,7 +650,6 @@ def wait_for_report_handler(queue: Queue, title: str, timeout: float = service.W
649
650
 
650
651
  def create_spinner(repetitions: int) -> Generator[str, None, None]:
651
652
  """A simple spinner that yields its individual characters."""
652
- assert repetitions > 0, "The number of repetitions should be greater than zero"
653
653
  while True:
654
654
  for ch in "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏":
655
655
  # Skip branch coverage, as it is not possible because of the assertion above
@@ -1,14 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from itertools import groupby
4
- from typing import Callable, Generator, Iterator
4
+ from typing import TYPE_CHECKING, Callable, Generator, Iterator
5
5
 
6
6
  import click
7
7
 
8
- from ..code_samples import CodeSampleStyle
9
8
  from ..exceptions import RuntimeErrorType
10
9
  from ..runner.serialization import SerializedCheck, deduplicate_failures
11
10
 
11
+ if TYPE_CHECKING:
12
+ from ..code_samples import CodeSampleStyle
13
+
12
14
  TEST_CASE_ID_TITLE = "Test Case ID"
13
15
 
14
16
 
@@ -6,11 +6,12 @@ from shlex import quote
6
6
  from typing import TYPE_CHECKING
7
7
 
8
8
  from .constants import SCHEMATHESIS_TEST_CASE_HEADER
9
- from .types import Headers
10
9
 
11
10
  if TYPE_CHECKING:
12
11
  from requests.structures import CaseInsensitiveDict
13
12
 
13
+ from .types import Headers
14
+
14
15
 
15
16
  @lru_cache
16
17
  def get_excluded_headers() -> CaseInsensitiveDict:
@@ -120,9 +121,9 @@ def _generate_requests(
120
121
  url = _escape_single_quotes(url)
121
122
  command = f"requests.{method.lower()}('{url}'"
122
123
  if body:
123
- command += f", data={repr(body)}"
124
+ command += f", data={body!r}"
124
125
  if headers:
125
- command += f", headers={repr(headers)}"
126
+ command += f", headers={headers!r}"
126
127
  if not verify:
127
128
  command += ", verify=False"
128
129
  command += ")"
@@ -13,8 +13,7 @@ if TYPE_CHECKING:
13
13
 
14
14
  def install() -> None:
15
15
  warnings.warn(
16
- "The `--contrib-unique-data` CLI option and the corresponding `schemathesis.contrib.unique_data` hook "
17
- "are **DEPRECATED**. The concept of this feature does not fit the core principles of Hypothesis where "
16
+ "The `schemathesis.contrib.unique_data` hook is **DEPRECATED**. The concept of this feature does not fit the core principles of Hypothesis where "
18
17
  "strategies are configurable on a per-example basis but this feature implies uniqueness across examples. "
19
18
  "This leads to cryptic error messages about external state and flaky test runs, "
20
19
  "therefore it will be removed in Schemathesis 4.0",
@@ -5,21 +5,22 @@ import re
5
5
  import traceback
6
6
  from dataclasses import dataclass, field
7
7
  from hashlib import sha1
8
- from json import JSONDecodeError
9
- from types import TracebackType
10
8
  from typing import TYPE_CHECKING, Any, Callable, Generator, NoReturn
11
9
 
12
10
  from .constants import SERIALIZERS_SUGGESTION_MESSAGE
13
- from .failures import FailureContext
14
11
  from .internal.output import truncate_json
15
12
 
16
13
  if TYPE_CHECKING:
14
+ from json import JSONDecodeError
15
+ from types import TracebackType
16
+
17
17
  import hypothesis.errors
18
18
  from graphql.error import GraphQLFormattedError
19
19
  from jsonschema import RefResolutionError, ValidationError
20
20
  from jsonschema import SchemaError as JsonSchemaError
21
21
  from requests import RequestException
22
22
 
23
+ from .failures import FailureContext
23
24
  from .transports.responses import GenericResponse
24
25
 
25
26
 
@@ -1,9 +1,12 @@
1
1
  from __future__ import annotations
2
2
 
3
- from flask import Flask
3
+ from typing import TYPE_CHECKING
4
4
 
5
5
  from . import _server
6
6
 
7
+ if TYPE_CHECKING:
8
+ from flask import Flask
9
+
7
10
 
8
11
  def run_server(app: Flask, port: int | None = None, timeout: float = 0.05) -> int:
9
12
  """Start a thread with the given aiohttp application."""
@@ -3,12 +3,11 @@ from __future__ import annotations
3
3
  import unittest
4
4
  from contextlib import contextmanager
5
5
  from functools import partial
6
- from typing import Any, Callable, Generator, Type, cast
6
+ from typing import TYPE_CHECKING, Any, Callable, Generator, Type, cast
7
7
 
8
8
  import pytest
9
9
  from _pytest import fixtures, nodes
10
10
  from _pytest.config import hookimpl
11
- from _pytest.fixtures import FuncFixtureInfo
12
11
  from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
13
12
  from hypothesis import reporting
14
13
  from hypothesis.errors import InvalidArgument, Unsatisfiable
@@ -30,7 +29,6 @@ from ..exceptions import (
30
29
  UsageError,
31
30
  )
32
31
  from ..internal.result import Ok, Result
33
- from ..models import APIOperation
34
32
  from ..utils import (
35
33
  PARAMETRIZE_MARKER,
36
34
  fail_on_no_matches,
@@ -42,6 +40,11 @@ from ..utils import (
42
40
  validate_given_args,
43
41
  )
44
42
 
43
+ if TYPE_CHECKING:
44
+ from _pytest.fixtures import FuncFixtureInfo
45
+
46
+ from ..models import APIOperation
47
+
45
48
 
46
49
  class SchemathesisFunction(Function):
47
50
  def __init__(
schemathesis/failures.py CHANGED
@@ -2,12 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import textwrap
4
4
  from dataclasses import dataclass
5
- from json import JSONDecodeError
6
5
  from typing import TYPE_CHECKING, Any
7
6
 
8
7
  from schemathesis.internal.output import OutputConfig
9
8
 
10
9
  if TYPE_CHECKING:
10
+ from json import JSONDecodeError
11
+
11
12
  from graphql.error import GraphQLFormattedError
12
13
  from jsonschema import ValidationError
13
14
 
schemathesis/filters.py CHANGED
@@ -54,7 +54,7 @@ class Matcher:
54
54
  func = partial(by_value_list, attribute=attribute, expected=expected)
55
55
  else:
56
56
  func = partial(by_value, attribute=attribute, expected=expected)
57
- label = f"{attribute}={repr(expected)}"
57
+ label = f"{attribute}={expected!r}"
58
58
  return cls(func, label=label, _hash=hash(label))
59
59
 
60
60
  @classmethod
@@ -68,7 +68,7 @@ class Matcher:
68
68
  flags = 0
69
69
  regex = re.compile(regex, flags=flags)
70
70
  func = partial(by_regex, attribute=attribute, regex=regex)
71
- label = f"{attribute}_regex={repr(regex)}"
71
+ label = f"{attribute}_regex={regex!r}"
72
72
  return cls(func, label=label, _hash=hash(label))
73
73
 
74
74
  def match(self, ctx: HasAPIOperation) -> bool:
@@ -4,8 +4,8 @@ import random
4
4
  from dataclasses import dataclass, field
5
5
  from typing import TYPE_CHECKING
6
6
 
7
- from ._hypothesis import add_single_example, combine_strategies, get_single_example # noqa: E402
8
- from ._methods import DataGenerationMethod, DataGenerationMethodInput # noqa: E402
7
+ from ._hypothesis import add_single_example, combine_strategies, get_single_example
8
+ from ._methods import DataGenerationMethod, DataGenerationMethodInput
9
9
 
10
10
  if TYPE_CHECKING:
11
11
  from hypothesis.strategies import SearchStrategy
@@ -9,7 +9,7 @@ if TYPE_CHECKING:
9
9
  from hypothesis import strategies as st
10
10
 
11
11
 
12
- @lru_cache()
12
+ @lru_cache
13
13
  def default_settings() -> settings:
14
14
  from hypothesis import HealthCheck, Phase, Verbosity, settings
15
15
 
@@ -4,7 +4,7 @@ import json
4
4
  from contextlib import contextmanager, suppress
5
5
  from dataclasses import dataclass, field
6
6
  from functools import lru_cache
7
- from typing import Any, Generator, Set, Type, TypeVar, cast
7
+ from typing import Any, Generator, TypeVar, cast
8
8
 
9
9
  import jsonschema
10
10
  from hypothesis import strategies as st
@@ -140,8 +140,8 @@ def _ignore_unfixable(
140
140
  *,
141
141
  # Cache exception types here as `jsonschema` uses a custom `__getattr__` on the module level
142
142
  # and it may cause errors during the interpreter shutdown
143
- ref_error: Type[Exception] = jsonschema.RefResolutionError,
144
- schema_error: Type[Exception] = jsonschema.SchemaError,
143
+ ref_error: type[Exception] = jsonschema.RefResolutionError,
144
+ schema_error: type[Exception] = jsonschema.SchemaError,
145
145
  ) -> Generator:
146
146
  try:
147
147
  yield
@@ -172,7 +172,7 @@ def cover_schema_iter(ctx: CoverageContext, schema: dict | bool) -> Generator[Ge
172
172
  yield from _cover_positive_for_type(ctx, schema, ty)
173
173
  if DataGenerationMethod.negative in ctx.data_generation_methods:
174
174
  template = None
175
- seen: Set[Any | tuple[type, str]] = set()
175
+ seen: set[Any | tuple[type, str]] = set()
176
176
  for key, value in schema.items():
177
177
  with _ignore_unfixable():
178
178
  if key == "enum":
@@ -238,7 +238,9 @@ def _get_properties(schema: dict | bool) -> dict | bool:
238
238
  if isinstance(schema, dict):
239
239
  if "example" in schema:
240
240
  return {"const": schema["example"]}
241
- if "examples" in schema and schema["examples"]:
241
+ if "default" in schema:
242
+ return {"const": schema["default"]}
243
+ if schema.get("examples"):
242
244
  return {"enum": schema["examples"]}
243
245
  if schema.get("type") == "object":
244
246
  return _get_template_schema(schema, "object")
@@ -265,12 +267,19 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
265
267
  max_length = schema.get("maxLength")
266
268
  example = schema.get("example")
267
269
  examples = schema.get("examples")
268
- if example or examples:
270
+ default = schema.get("default")
271
+ if example or examples or default:
269
272
  if example:
270
273
  yield PositiveValue(example)
271
274
  if examples:
272
275
  for example in examples:
273
276
  yield PositiveValue(example)
277
+ if (
278
+ default
279
+ and not (example is not None and default == example)
280
+ and not (examples is not None and any(default == ex for ex in examples))
281
+ ):
282
+ yield PositiveValue(default)
274
283
  elif not min_length and not max_length:
275
284
  # Default positive value
276
285
  yield PositiveValue(ctx.generate_from_schema(schema))
@@ -327,13 +336,20 @@ def _positive_number(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
327
336
  multiple_of = schema.get("multipleOf")
328
337
  example = schema.get("example")
329
338
  examples = schema.get("examples")
339
+ default = schema.get("default")
330
340
 
331
- if example or examples:
341
+ if example or examples or default:
332
342
  if example:
333
343
  yield PositiveValue(example)
334
344
  if examples:
335
345
  for example in examples:
336
346
  yield PositiveValue(example)
347
+ if (
348
+ default
349
+ and not (example is not None and default == example)
350
+ and not (examples is not None and any(default == ex for ex in examples))
351
+ ):
352
+ yield PositiveValue(default)
337
353
  elif not minimum and not maximum:
338
354
  # Default positive value
339
355
  yield PositiveValue(ctx.generate_from_schema(schema))
@@ -382,13 +398,20 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
382
398
  seen = set()
383
399
  example = schema.get("example")
384
400
  examples = schema.get("examples")
401
+ default = schema.get("default")
385
402
 
386
- if example or examples:
403
+ if example or examples or default:
387
404
  if example:
388
405
  yield PositiveValue(example)
389
406
  if examples:
390
407
  for example in examples:
391
408
  yield PositiveValue(example)
409
+ if (
410
+ default
411
+ and not (example is not None and default == example)
412
+ and not (examples is not None and any(default == ex for ex in examples))
413
+ ):
414
+ yield PositiveValue(default)
392
415
  else:
393
416
  yield PositiveValue(template)
394
417
  seen.add(len(template))
@@ -425,19 +448,37 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
425
448
  def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator[GeneratedValue, None, None]:
426
449
  example = schema.get("example")
427
450
  examples = schema.get("examples")
451
+ default = schema.get("default")
428
452
 
429
- if example or examples:
453
+ if example or examples or default:
430
454
  if example:
431
455
  yield PositiveValue(example)
432
456
  if examples:
433
457
  for example in examples:
434
458
  yield PositiveValue(example)
459
+ if (
460
+ default
461
+ and not (example is not None and default == example)
462
+ and not (examples is not None and any(default == ex for ex in examples))
463
+ ):
464
+ yield PositiveValue(default)
465
+
435
466
  else:
436
467
  yield PositiveValue(template)
437
- # Only required properties
468
+
438
469
  properties = schema.get("properties", {})
439
- if set(properties) != set(schema.get("required", {})):
440
- only_required = {k: v for k, v in template.items() if k in schema.get("required", [])}
470
+ required = set(schema.get("required", []))
471
+ optional = list(set(properties) - required)
472
+ optional.sort()
473
+
474
+ # Generate combinations with required properties and one optional property
475
+ for name in optional:
476
+ combo = {k: v for k, v in template.items() if k in required or k == name}
477
+ if combo != template:
478
+ yield PositiveValue(combo)
479
+ # Generate only required properties
480
+ if set(properties) != required:
481
+ only_required = {k: v for k, v in template.items() if k in required}
441
482
  yield PositiveValue(only_required)
442
483
  seen = set()
443
484
  for name, sub_schema in properties.items():
schemathesis/graphql.py CHANGED
@@ -1,4 +1,3 @@
1
- # Public API
2
1
  from .specs.graphql import nodes # noqa: F401
3
2
  from .specs.graphql.loaders import from_asgi, from_dict, from_file, from_path, from_url, from_wsgi # noqa: F401
4
3
  from .specs.graphql.scalars import scalar # noqa: F401
schemathesis/hooks.py CHANGED
@@ -6,11 +6,10 @@ from copy import deepcopy
6
6
  from dataclasses import dataclass, field
7
7
  from enum import Enum, unique
8
8
  from functools import partial
9
- from typing import TYPE_CHECKING, Any, Callable, ClassVar, DefaultDict, cast
9
+ from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast
10
10
 
11
11
  from .filters import FilterSet, attach_filter_chain
12
12
  from .internal.deprecation import deprecated_property
13
- from .types import GenericTest
14
13
 
15
14
  if TYPE_CHECKING:
16
15
  from hypothesis import strategies as st
@@ -18,6 +17,7 @@ if TYPE_CHECKING:
18
17
  from .models import APIOperation, Case
19
18
  from .schemas import BaseSchema
20
19
  from .transports.responses import GenericResponse
20
+ from .types import GenericTest
21
21
 
22
22
 
23
23
  @unique
@@ -108,7 +108,7 @@ class HookDispatcher:
108
108
  """
109
109
 
110
110
  scope: HookScope
111
- _hooks: DefaultDict[str, list[Callable]] = field(default_factory=lambda: defaultdict(list))
111
+ _hooks: defaultdict[str, list[Callable]] = field(default_factory=lambda: defaultdict(list))
112
112
  _specs: ClassVar[dict[str, RegisteredHook]] = {}
113
113
 
114
114
  def __post_init__(self) -> None:
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import warnings
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Callable, Optional
7
+
8
+ if TYPE_CHECKING:
9
+ from ..models import Case
10
+ from ..transports.responses import GenericResponse
11
+ from requests.structures import CaseInsensitiveDict
12
+
13
+
14
+ CheckFunction = Callable[["CheckContext", "GenericResponse", "Case"], Optional[bool]]
15
+
16
+
17
+ @dataclass
18
+ class CheckContext:
19
+ """Context for Schemathesis checks.
20
+
21
+ Provides access to broader test execution data beyond individual test cases.
22
+ """
23
+
24
+ headers: CaseInsensitiveDict | None = None
25
+
26
+
27
+ def wrap_check(check: Callable) -> CheckFunction:
28
+ """Make older checks compatible with the new signature."""
29
+ signature = inspect.signature(check)
30
+ parameters = len(signature.parameters)
31
+
32
+ if parameters == 3:
33
+ # New style check, return as is
34
+ return check
35
+
36
+ if parameters == 2:
37
+ # Old style check, wrap it
38
+ warnings.warn(
39
+ f"The check function '{check.__name__}' uses an outdated signature. "
40
+ "Please update it to accept 'ctx' as the first argument: "
41
+ "(ctx: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]",
42
+ DeprecationWarning,
43
+ stacklevel=2,
44
+ )
45
+
46
+ def wrapper(_: CheckContext, response: GenericResponse, case: Case) -> Optional[bool]:
47
+ return check(response, case)
48
+
49
+ wrapper.__name__ = check.__name__
50
+
51
+ return wrapper
52
+
53
+ raise ValueError(f"Invalid check function signature. Expected 2 or 3 parameters, got {parameters}")
schemathesis/lazy.py CHANGED
@@ -2,15 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from inspect import signature
5
- from typing import Any, Callable, Generator
5
+ from typing import TYPE_CHECKING, Any, Callable, Generator
6
6
 
7
7
  import pytest
8
- from _pytest.fixtures import FixtureRequest
9
8
  from hypothesis.core import HypothesisHandle
10
9
  from hypothesis.errors import Flaky
11
10
  from hypothesis.internal.escalation import format_exception, get_trimmed_traceback
12
11
  from hypothesis.internal.reflection import impersonate
13
- from pyrate_limiter import Limiter
14
12
  from pytest_subtests import SubTests, nullcontext
15
13
 
16
14
  from ._compat import MultipleFailures, get_interesting_origin
@@ -20,14 +18,10 @@ from .code_samples import CodeSampleStyle
20
18
  from .constants import FLAKY_FAILURE_MESSAGE, NOT_SET
21
19
  from .exceptions import CheckFailed, OperationSchemaError, SkipTest, get_grouped_exception
22
20
  from .filters import FilterSet, FilterValue, MatcherFunc, RegexValue, filter_set_from_components, is_deprecated
23
- from .generation import DataGenerationMethodInput, GenerationConfig
24
21
  from .hooks import HookDispatcher, HookScope
25
22
  from .internal.deprecation import warn_filtration_arguments
26
- from .internal.output import OutputConfig
27
23
  from .internal.result import Ok
28
- from .models import APIOperation
29
24
  from .schemas import BaseSchema
30
- from .types import Filter, GenericTest, NotSet
31
25
  from .utils import (
32
26
  GivenInput,
33
27
  fail_on_no_matches,
@@ -39,6 +33,15 @@ from .utils import (
39
33
  validate_given_args,
40
34
  )
41
35
 
36
+ if TYPE_CHECKING:
37
+ from _pytest.fixtures import FixtureRequest
38
+ from pyrate_limiter import Limiter
39
+
40
+ from .generation import DataGenerationMethodInput, GenerationConfig
41
+ from .internal.output import OutputConfig
42
+ from .models import APIOperation
43
+ from .types import Filter, GenericTest, NotSet
44
+
42
45
 
43
46
  @dataclass
44
47
  class LazySchema:
schemathesis/loaders.py CHANGED
@@ -51,13 +51,13 @@ def _raise_for_status(response: GenericResponse) -> None:
51
51
  else:
52
52
  type_ = SchemaErrorType.HTTP_CLIENT_ERROR
53
53
  else:
54
- return None
54
+ return
55
55
  raise SchemaError(message=message, type=type_, url=response.request.url, response=response, extras=[])
56
56
 
57
57
 
58
58
  def load_app(path: str) -> Any:
59
59
  """Import an application from a string."""
60
- path, name = (re.split(r":(?![\\/])", path, maxsplit=1) + [""])[:2]
60
+ path, name = ([*re.split(":(?![\\\\/])", path, maxsplit=1), ""])[:2]
61
61
  __import__(path)
62
62
  # accessing the module from sys.modules returns a proper module, while `__import__`
63
63
  # may return a parent module (system dependent)
@@ -92,7 +92,7 @@ def get_yaml_loader() -> type[yaml.SafeLoader]:
92
92
  |[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+\.[0-9_]*
93
93
  |[-+]?\.(?:inf|Inf|INF)
94
94
  |\.(?:nan|NaN|NAN))$""",
95
- re.X,
95
+ re.VERBOSE,
96
96
  ),
97
97
  list("-+0123456789."),
98
98
  )