schemathesis 3.32.0__py3-none-any.whl → 3.32.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.
schemathesis/filters.py CHANGED
@@ -8,6 +8,8 @@ from functools import partial
8
8
  from types import SimpleNamespace
9
9
  from typing import TYPE_CHECKING, Callable, List, Protocol, Union
10
10
 
11
+ from .types import NotSet, Filter as FilterType
12
+
11
13
  from .exceptions import UsageError
12
14
 
13
15
  if TYPE_CHECKING:
@@ -58,7 +60,12 @@ class Matcher:
58
60
  def for_regex(cls, attribute: str, regex: RegexValue) -> Matcher:
59
61
  """Matcher that checks whether the specified attribute has the provided regex."""
60
62
  if isinstance(regex, str):
61
- regex = re.compile(regex)
63
+ flags: re.RegexFlag | int
64
+ if attribute == "method":
65
+ flags = re.IGNORECASE
66
+ else:
67
+ flags = 0
68
+ regex = re.compile(regex, flags=flags)
62
69
  func = partial(by_regex, attribute=attribute, regex=regex)
63
70
  label = f"{attribute}_regex={repr(regex)}"
64
71
  return cls(func, label=label, _hash=hash(label))
@@ -71,6 +78,8 @@ class Matcher:
71
78
  def get_operation_attribute(operation: APIOperation, attribute: str) -> str | list[str] | None:
72
79
  if attribute == "tag":
73
80
  return operation.tags
81
+ if attribute == "operation_id":
82
+ return operation.definition.raw.get("operationId")
74
83
  # Just uppercase `method`
75
84
  value = getattr(operation, attribute)
76
85
  if attribute == "method":
@@ -101,8 +110,8 @@ def by_regex(ctx: HasAPIOperation, attribute: str, regex: re.Pattern) -> bool:
101
110
  if value is None:
102
111
  return False
103
112
  if isinstance(value, list):
104
- return any(bool(regex.match(entry)) for entry in value)
105
- return bool(regex.match(value))
113
+ return any(bool(regex.search(entry)) for entry in value)
114
+ return bool(regex.search(value))
106
115
 
107
116
 
108
117
  @dataclass(repr=False, frozen=True)
@@ -111,6 +120,8 @@ class Filter:
111
120
 
112
121
  matchers: tuple[Matcher, ...]
113
122
 
123
+ __slots__ = ("matchers",)
124
+
114
125
  def __repr__(self) -> str:
115
126
  inner = " && ".join(matcher.label for matcher in self.matchers)
116
127
  return f"<{self.__class__.__name__}: [{inner}]>"
@@ -127,8 +138,14 @@ class Filter:
127
138
  class FilterSet:
128
139
  """Combines multiple filters to apply inclusion and exclusion rules on API operations."""
129
140
 
130
- _includes: set[Filter] = field(default_factory=set)
131
- _excludes: set[Filter] = field(default_factory=set)
141
+ _includes: set[Filter]
142
+ _excludes: set[Filter]
143
+
144
+ __slots__ = ("_includes", "_excludes")
145
+
146
+ def __init__(self) -> None:
147
+ self._includes = set()
148
+ self._excludes = set()
132
149
 
133
150
  def apply_to(self, operations: list[APIOperation]) -> list[APIOperation]:
134
151
  """Get a filtered list of the given operations that match the filters."""
@@ -168,6 +185,8 @@ class FilterSet:
168
185
  path_regex: RegexValue | None = None,
169
186
  tag: FilterValue | None = None,
170
187
  tag_regex: RegexValue | None = None,
188
+ operation_id: FilterValue | None = None,
189
+ operation_id_regex: RegexValue | None = None,
171
190
  ) -> None:
172
191
  """Add a new INCLUDE filter."""
173
192
  self._add_filter(
@@ -181,6 +200,8 @@ class FilterSet:
181
200
  path_regex=path_regex,
182
201
  tag=tag,
183
202
  tag_regex=tag_regex,
203
+ operation_id=operation_id,
204
+ operation_id_regex=operation_id_regex,
184
205
  )
185
206
 
186
207
  def exclude(
@@ -195,6 +216,8 @@ class FilterSet:
195
216
  path_regex: RegexValue | None = None,
196
217
  tag: FilterValue | None = None,
197
218
  tag_regex: RegexValue | None = None,
219
+ operation_id: FilterValue | None = None,
220
+ operation_id_regex: RegexValue | None = None,
198
221
  ) -> None:
199
222
  """Add a new EXCLUDE filter."""
200
223
  self._add_filter(
@@ -208,6 +231,8 @@ class FilterSet:
208
231
  path_regex=path_regex,
209
232
  tag=tag,
210
233
  tag_regex=tag_regex,
234
+ operation_id=operation_id,
235
+ operation_id_regex=operation_id_regex,
211
236
  )
212
237
 
213
238
  def _add_filter(
@@ -223,6 +248,8 @@ class FilterSet:
223
248
  path_regex: RegexValue | None = None,
224
249
  tag: FilterValue | None = None,
225
250
  tag_regex: RegexValue | None = None,
251
+ operation_id: FilterValue | None = None,
252
+ operation_id_regex: RegexValue | None = None,
226
253
  ) -> None:
227
254
  matchers = []
228
255
  if func is not None:
@@ -232,6 +259,7 @@ class FilterSet:
232
259
  ("method", method, method_regex),
233
260
  ("path", path, path_regex),
234
261
  ("tag", tag, tag_regex),
262
+ ("operation_id", operation_id, operation_id_regex),
235
263
  ):
236
264
  if expected is not None and regex is not None:
237
265
  # To match anything the regex should match the expected value, hence passing them together is useless
@@ -295,3 +323,74 @@ def attach_filter_chain(
295
323
  proxy.__name__ = attribute
296
324
 
297
325
  setattr(target, attribute, proxy)
326
+
327
+
328
+ def filter_set_from_components(
329
+ *,
330
+ include: bool,
331
+ method: FilterType | None = None,
332
+ endpoint: FilterType | None = None,
333
+ tag: FilterType | None = None,
334
+ operation_id: FilterType | None = None,
335
+ skip_deprecated_operations: bool | None | NotSet = None,
336
+ parent: FilterSet | None = None,
337
+ ) -> FilterSet:
338
+ def _is_defined(x: FilterType | None) -> bool:
339
+ return x is not None and not isinstance(x, NotSet)
340
+
341
+ def _is_deprecated(ctx: HasAPIOperation) -> bool:
342
+ return ctx.operation.definition.raw.get("deprecated") is True
343
+
344
+ def _prepare_filter(filter_: FilterType | None) -> RegexValue | None:
345
+ if filter_ is None or isinstance(filter_, NotSet):
346
+ return None
347
+ if isinstance(filter_, str):
348
+ return filter_
349
+ return "|".join(f"({f})" for f in filter_)
350
+
351
+ new = FilterSet()
352
+
353
+ if _is_defined(method) or _is_defined(endpoint) or _is_defined(tag) or _is_defined(operation_id):
354
+ new._add_filter(
355
+ include,
356
+ method_regex=_prepare_filter(method),
357
+ path_regex=_prepare_filter(endpoint),
358
+ tag_regex=_prepare_filter(tag),
359
+ operation_id_regex=_prepare_filter(operation_id),
360
+ )
361
+ if skip_deprecated_operations is True and not any(
362
+ matcher.label == _is_deprecated.__name__ for exclude_ in new._excludes for matcher in exclude_.matchers
363
+ ):
364
+ new.exclude(func=_is_deprecated)
365
+ # Merge with the parent filter set
366
+ if parent is not None:
367
+ for include_ in parent._includes:
368
+ matchers = include_.matchers
369
+ ids = []
370
+ for idx, matcher in enumerate(matchers):
371
+ label = matcher.label
372
+ if (
373
+ (not isinstance(method, NotSet) and label.startswith("method_regex="))
374
+ or (not isinstance(endpoint, NotSet) and label.startswith("path_regex="))
375
+ or (not isinstance(tag, NotSet) and matcher.label.startswith("tag_regex="))
376
+ or (not isinstance(operation_id, NotSet) and matcher.label.startswith("operation_id_regex="))
377
+ ):
378
+ ids.append(idx)
379
+ if ids:
380
+ matchers = tuple(matcher for idx, matcher in enumerate(matchers) if idx not in ids)
381
+ if matchers:
382
+ if new._includes:
383
+ existing = new._includes.pop()
384
+ matchers = existing.matchers + matchers
385
+ new._includes.add(Filter(matchers=matchers))
386
+ for exclude_ in parent._excludes:
387
+ matchers = exclude_.matchers
388
+ ids = []
389
+ for idx, matcher in enumerate(exclude_.matchers):
390
+ if skip_deprecated_operations is False and matcher.label == _is_deprecated.__name__:
391
+ ids.append(idx)
392
+ if ids:
393
+ matchers = tuple(matcher for idx, matcher in enumerate(matchers) if idx not in ids)
394
+ if matchers:
395
+ new._excludes.add(exclude_)
396
+ return new
schemathesis/lazy.py CHANGED
@@ -19,6 +19,7 @@ from .auths import AuthStorage
19
19
  from .code_samples import CodeSampleStyle
20
20
  from .constants import FLAKY_FAILURE_MESSAGE, NOT_SET
21
21
  from .exceptions import CheckFailed, OperationSchemaError, SkipTest, get_grouped_exception
22
+ from .filters import filter_set_from_components
22
23
  from .generation import DataGenerationMethodInput, GenerationConfig
23
24
  from .hooks import HookDispatcher, HookScope
24
25
  from .internal.output import OutputConfig
@@ -341,18 +342,24 @@ def get_schema(
341
342
  schema = request.getfixturevalue(name)
342
343
  if not isinstance(schema, BaseSchema):
343
344
  raise ValueError(f"The given schema must be an instance of BaseSchema, got: {type(schema)}")
344
- return schema.clone(
345
- base_url=base_url,
345
+
346
+ filter_set = filter_set_from_components(
347
+ include=True,
346
348
  method=method,
347
349
  endpoint=endpoint,
348
350
  tag=tag,
349
351
  operation_id=operation_id,
352
+ skip_deprecated_operations=skip_deprecated_operations,
353
+ parent=schema.filter_set,
354
+ )
355
+ return schema.clone(
356
+ base_url=base_url,
357
+ filter_set=filter_set,
350
358
  app=app,
351
359
  test_function=test_function,
352
360
  hooks=schema.hooks.merge(hooks),
353
361
  auth=auth,
354
362
  validate_schema=validate_schema,
355
- skip_deprecated_operations=skip_deprecated_operations,
356
363
  data_generation_methods=data_generation_methods,
357
364
  generation_config=generation_config,
358
365
  output_config=output_config,
@@ -9,7 +9,8 @@ from typing import TYPE_CHECKING, Any
9
9
  from ..exceptions import RuntimeErrorType, SchemaError, SchemaErrorType, format_exception
10
10
  from ..generation import DataGenerationMethod
11
11
  from ..internal.datetime import current_datetime
12
- from ..internal.result import Result
12
+ from ..internal.result import Err, Ok, Result
13
+ from ..service.models import AnalysisSuccess
13
14
  from .serialization import SerializedError, SerializedTestResult
14
15
 
15
16
  if TYPE_CHECKING:
@@ -105,6 +106,23 @@ class BeforeAnalysis(ExecutionEvent):
105
106
  class AfterAnalysis(ExecutionEvent):
106
107
  analysis: Result[AnalysisResult, Exception] | None
107
108
 
109
+ def _serialize(self) -> dict[str, Any]:
110
+ data = {}
111
+ if isinstance(self.analysis, Ok):
112
+ result = self.analysis.ok()
113
+ if isinstance(result, AnalysisSuccess):
114
+ data["analysis_id"] = result.id
115
+ else:
116
+ data["error"] = result.message
117
+ elif isinstance(self.analysis, Err):
118
+ data["error"] = format_exception(self.analysis.err())
119
+ return data
120
+
121
+ def asdict(self, **kwargs: Any) -> dict[str, Any]:
122
+ data = self._serialize()
123
+ data["event_type"] = self.__class__.__name__
124
+ return data
125
+
108
126
 
109
127
  class CurrentOperationMixin:
110
128
  method: str
@@ -296,6 +314,9 @@ class StatefulEvent(ExecutionEvent):
296
314
 
297
315
  __slots__ = ("data",)
298
316
 
317
+ def asdict(self, **kwargs: Any) -> dict[str, Any]:
318
+ return {"data": self.data.asdict(**kwargs), "event_type": self.__class__.__name__}
319
+
299
320
 
300
321
  @dataclass
301
322
  class AfterStatefulExecution(ExecutionEvent):
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
  import logging
9
9
  import re
10
10
  import textwrap
11
- from dataclasses import dataclass, field
11
+ from dataclasses import asdict, dataclass, field
12
12
  from typing import TYPE_CHECKING, Any, cast
13
13
 
14
14
  from ..code_samples import get_excluded_headers
@@ -453,3 +453,72 @@ def deduplicate_failures(checks: list[SerializedCheck]) -> list[SerializedCheck]
453
453
  unique_checks.append(check)
454
454
  seen.add(key)
455
455
  return unique_checks
456
+
457
+
458
+ def _serialize_case(case: SerializedCase) -> dict[str, Any]:
459
+ return {
460
+ "id": case.id,
461
+ "generation_time": case.generation_time,
462
+ "verbose_name": case.verbose_name,
463
+ "path_template": case.path_template,
464
+ "path_parameters": stringify_path_parameters(case.path_parameters),
465
+ "query": prepare_query(case.query),
466
+ "cookies": case.cookies,
467
+ "media_type": case.media_type,
468
+ }
469
+
470
+
471
+ def _serialize_response(response: Response) -> dict[str, Any]:
472
+ return {
473
+ "status_code": response.status_code,
474
+ "headers": response.headers,
475
+ "body": response.body,
476
+ "encoding": response.encoding,
477
+ "elapsed": response.elapsed,
478
+ }
479
+
480
+
481
+ def _serialize_check(check: SerializedCheck) -> dict[str, Any]:
482
+ return {
483
+ "name": check.name,
484
+ "value": check.value,
485
+ "request": {
486
+ "method": check.request.method,
487
+ "uri": check.request.uri,
488
+ "body": check.request.body,
489
+ "headers": check.request.headers,
490
+ },
491
+ "response": _serialize_response(check.response) if check.response is not None else None,
492
+ "example": _serialize_case(check.example),
493
+ "message": check.message,
494
+ "context": asdict(check.context) if check.context is not None else None, # type: ignore
495
+ "history": [
496
+ {"case": _serialize_case(entry.case), "response": _serialize_response(entry.response)}
497
+ for entry in check.history
498
+ ],
499
+ }
500
+
501
+
502
+ def stringify_path_parameters(path_parameters: dict[str, Any] | None) -> dict[str, str]:
503
+ """Cast all path parameter values to strings.
504
+
505
+ Path parameter values may be of arbitrary type, but to display them properly they should be casted to strings.
506
+ """
507
+ return {key: str(value) for key, value in (path_parameters or {}).items()}
508
+
509
+
510
+ def prepare_query(query: dict[str, Any] | None) -> dict[str, list[str]]:
511
+ """Convert all query values to list of strings.
512
+
513
+ Query parameters may be generated in different shapes, including integers, strings, list of strings, etc.
514
+ It can also be an object, if the schema contains an object, but `style` and `explode` combo is not applicable.
515
+ """
516
+
517
+ def to_list_of_strings(value: Any) -> list[str]:
518
+ if isinstance(value, list):
519
+ return list(map(str, value))
520
+ if isinstance(value, str):
521
+ return [value]
522
+ return [str(value)]
523
+
524
+ return {key: to_list_of_strings(value) for key, value in (query or {}).items()}
schemathesis/schemas.py CHANGED
@@ -37,6 +37,7 @@ from .auths import AuthStorage
37
37
  from .code_samples import CodeSampleStyle
38
38
  from .constants import NOT_SET
39
39
  from .exceptions import OperationSchemaError, UsageError
40
+ from .filters import FilterSet, filter_set_from_components
40
41
  from .generation import (
41
42
  DEFAULT_DATA_GENERATION_METHODS,
42
43
  DataGenerationMethod,
@@ -81,16 +82,12 @@ class BaseSchema(Mapping):
81
82
  transport: Transport
82
83
  location: str | None = None
83
84
  base_url: str | None = None
84
- method: Filter | None = None
85
- endpoint: Filter | None = None
86
- tag: Filter | None = None
87
- operation_id: Filter | None = None
85
+ filter_set: FilterSet = field(default_factory=FilterSet)
88
86
  app: Any = None
89
87
  hooks: HookDispatcher = field(default_factory=lambda: HookDispatcher(scope=HookScope.SCHEMA))
90
88
  auth: AuthStorage = field(default_factory=AuthStorage)
91
89
  test_function: GenericTest | None = None
92
90
  validate_schema: bool = True
93
- skip_deprecated_operations: bool = False
94
91
  data_generation_methods: list[DataGenerationMethod] = field(
95
92
  default_factory=lambda: list(DEFAULT_DATA_GENERATION_METHODS)
96
93
  )
@@ -242,6 +239,16 @@ class BaseSchema(Mapping):
242
239
  CodeSampleStyle.from_str(code_sample_style) if isinstance(code_sample_style, str) else code_sample_style
243
240
  )
244
241
 
242
+ filter_set = filter_set_from_components(
243
+ include=True,
244
+ method=method,
245
+ endpoint=endpoint,
246
+ tag=tag,
247
+ operation_id=operation_id,
248
+ skip_deprecated_operations=skip_deprecated_operations,
249
+ parent=self.filter_set,
250
+ )
251
+
245
252
  def wrapper(func: GenericTest) -> GenericTest:
246
253
  if hasattr(func, PARAMETRIZE_MARKER):
247
254
 
@@ -256,13 +263,9 @@ class BaseSchema(Mapping):
256
263
  HookDispatcher.add_dispatcher(func)
257
264
  cloned = self.clone(
258
265
  test_function=func,
259
- method=method,
260
- endpoint=endpoint,
261
- tag=tag,
262
- operation_id=operation_id,
263
266
  validate_schema=validate_schema,
264
- skip_deprecated_operations=skip_deprecated_operations,
265
267
  data_generation_methods=data_generation_methods,
268
+ filter_set=filter_set,
266
269
  code_sample_style=_code_sample_style, # type: ignore
267
270
  )
268
271
  setattr(func, PARAMETRIZE_MARKER, cloned)
@@ -279,38 +282,26 @@ class BaseSchema(Mapping):
279
282
  *,
280
283
  base_url: str | None | NotSet = NOT_SET,
281
284
  test_function: GenericTest | None = None,
282
- method: Filter | None = NOT_SET,
283
- endpoint: Filter | None = NOT_SET,
284
- tag: Filter | None = NOT_SET,
285
- operation_id: Filter | None = NOT_SET,
286
285
  app: Any = NOT_SET,
287
286
  hooks: HookDispatcher | NotSet = NOT_SET,
288
287
  auth: AuthStorage | NotSet = NOT_SET,
289
288
  validate_schema: bool | NotSet = NOT_SET,
290
- skip_deprecated_operations: bool | NotSet = NOT_SET,
291
289
  data_generation_methods: DataGenerationMethodInput | NotSet = NOT_SET,
292
290
  generation_config: GenerationConfig | NotSet = NOT_SET,
293
291
  output_config: OutputConfig | NotSet = NOT_SET,
294
292
  code_sample_style: CodeSampleStyle | NotSet = NOT_SET,
295
293
  rate_limiter: Limiter | None = NOT_SET,
296
294
  sanitize_output: bool | NotSet | None = NOT_SET,
295
+ filter_set: FilterSet | None = None,
297
296
  ) -> BaseSchema:
298
297
  if base_url is NOT_SET:
299
298
  base_url = self.base_url
300
- if method is NOT_SET:
301
- method = self.method
302
- if endpoint is NOT_SET:
303
- endpoint = self.endpoint
304
- if tag is NOT_SET:
305
- tag = self.tag
306
- if operation_id is NOT_SET:
307
- operation_id = self.operation_id
308
299
  if app is NOT_SET:
309
300
  app = self.app
310
301
  if validate_schema is NOT_SET:
311
302
  validate_schema = self.validate_schema
312
- if skip_deprecated_operations is NOT_SET:
313
- skip_deprecated_operations = self.skip_deprecated_operations
303
+ if filter_set is None:
304
+ filter_set = self.filter_set
314
305
  if hooks is NOT_SET:
315
306
  hooks = self.hooks
316
307
  if auth is NOT_SET:
@@ -332,22 +323,18 @@ class BaseSchema(Mapping):
332
323
  self.raw_schema,
333
324
  location=self.location,
334
325
  base_url=base_url, # type: ignore
335
- method=method,
336
- endpoint=endpoint,
337
- tag=tag,
338
- operation_id=operation_id,
339
326
  app=app,
340
327
  hooks=hooks, # type: ignore
341
328
  auth=auth, # type: ignore
342
329
  test_function=test_function,
343
330
  validate_schema=validate_schema, # type: ignore
344
- skip_deprecated_operations=skip_deprecated_operations, # type: ignore
345
331
  data_generation_methods=data_generation_methods, # type: ignore
346
332
  generation_config=generation_config, # type: ignore
347
333
  output_config=output_config, # type: ignore
348
334
  code_sample_style=code_sample_style, # type: ignore
349
335
  rate_limiter=rate_limiter, # type: ignore
350
336
  sanitize_output=sanitize_output, # type: ignore
337
+ filter_set=filter_set, # type: ignore
351
338
  transport=self.transport,
352
339
  )
353
340
 
@@ -3,14 +3,10 @@ from __future__ import annotations
3
3
  from dataclasses import asdict
4
4
  from typing import Any, Callable, Dict, Optional, TypeVar, cast
5
5
 
6
- from ..exceptions import format_exception
7
- from ..internal.result import Err, Ok
8
6
  from ..internal.transformation import merge_recursively
9
- from ..models import Response
10
7
  from ..runner import events
11
- from ..runner.serialization import SerializedCase, SerializedCheck
8
+ from ..runner.serialization import _serialize_check
12
9
  from ..stateful import events as stateful_events
13
- from .models import AnalysisSuccess
14
10
 
15
11
  S = TypeVar("S", bound=events.ExecutionEvent)
16
12
  SerializeFunc = Callable[[S], Optional[Dict[str, Any]]]
@@ -38,17 +34,7 @@ def serialize_before_analysis(_: events.BeforeAnalysis) -> None:
38
34
 
39
35
 
40
36
  def serialize_after_analysis(event: events.AfterAnalysis) -> dict[str, Any] | None:
41
- data = {}
42
- analysis = event.analysis
43
- if isinstance(analysis, Ok):
44
- result = analysis.ok()
45
- if isinstance(result, AnalysisSuccess):
46
- data["analysis_id"] = result.id
47
- else:
48
- data["error"] = result.message
49
- elif isinstance(analysis, Err):
50
- data["error"] = format_exception(analysis.err())
51
- return data
37
+ return event._serialize()
52
38
 
53
39
 
54
40
  def serialize_before_execution(event: events.BeforeExecution) -> dict[str, Any] | None:
@@ -59,50 +45,6 @@ def serialize_before_execution(event: events.BeforeExecution) -> dict[str, Any]
59
45
  }
60
46
 
61
47
 
62
- def _serialize_case(case: SerializedCase) -> dict[str, Any]:
63
- return {
64
- "id": case.id,
65
- "generation_time": case.generation_time,
66
- "verbose_name": case.verbose_name,
67
- "path_template": case.path_template,
68
- "path_parameters": stringify_path_parameters(case.path_parameters),
69
- "query": prepare_query(case.query),
70
- "cookies": case.cookies,
71
- "media_type": case.media_type,
72
- }
73
-
74
-
75
- def _serialize_response(response: Response) -> dict[str, Any]:
76
- return {
77
- "status_code": response.status_code,
78
- "headers": response.headers,
79
- "body": response.body,
80
- "encoding": response.encoding,
81
- "elapsed": response.elapsed,
82
- }
83
-
84
-
85
- def _serialize_check(check: SerializedCheck) -> dict[str, Any]:
86
- return {
87
- "name": check.name,
88
- "value": check.value,
89
- "request": {
90
- "method": check.request.method,
91
- "uri": check.request.uri,
92
- "body": check.request.body,
93
- "headers": check.request.headers,
94
- },
95
- "response": _serialize_response(check.response) if check.response is not None else None,
96
- "example": _serialize_case(check.example),
97
- "message": check.message,
98
- "context": asdict(check.context) if check.context is not None else None, # type: ignore
99
- "history": [
100
- {"case": _serialize_case(entry.case), "response": _serialize_response(entry.response)}
101
- for entry in check.history
102
- ],
103
- }
104
-
105
-
106
48
  def serialize_after_execution(event: events.AfterExecution) -> dict[str, Any] | None:
107
49
  return {
108
50
  "correlation_id": event.correlation_id,
@@ -154,45 +96,7 @@ def serialize_stateful_event(event: events.StatefulEvent) -> dict[str, Any] | No
154
96
 
155
97
 
156
98
  def _serialize_stateful_event(event: stateful_events.StatefulEvent) -> dict[str, Any] | None:
157
- data: dict[str, Any]
158
- if isinstance(event, stateful_events.RunStarted):
159
- data = {
160
- "timestamp": event.timestamp,
161
- "started_at": event.started_at,
162
- }
163
- elif isinstance(event, stateful_events.SuiteFinished):
164
- data = {
165
- "timestamp": event.timestamp,
166
- "status": event.status,
167
- "failures": [_serialize_check(SerializedCheck.from_check(failure)) for failure in event.failures],
168
- }
169
- elif isinstance(event, stateful_events.Errored):
170
- data = {
171
- "timestamp": event.timestamp,
172
- "exception": format_exception(event.exception, True),
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
- }
193
- else:
194
- data = asdict(event)
195
- return {"data": {event.__class__.__name__: data}}
99
+ return {"data": {event.__class__.__name__: event.asdict()}}
196
100
 
197
101
 
198
102
  def serialize_after_stateful_execution(event: events.AfterStatefulExecution) -> dict[str, Any] | None:
@@ -264,28 +168,3 @@ def serialize_event(
264
168
  data = merge_recursively(data, extra)
265
169
  # Externally tagged structure
266
170
  return {event.__class__.__name__: data}
267
-
268
-
269
- def stringify_path_parameters(path_parameters: dict[str, Any] | None) -> dict[str, str]:
270
- """Cast all path parameter values to strings.
271
-
272
- Path parameter values may be of arbitrary type, but to display them properly they should be casted to strings.
273
- """
274
- return {key: str(value) for key, value in (path_parameters or {}).items()}
275
-
276
-
277
- def prepare_query(query: dict[str, Any] | None) -> dict[str, list[str]]:
278
- """Convert all query values to list of strings.
279
-
280
- Query parameters may be generated in different shapes, including integers, strings, list of strings, etc.
281
- It can also be an object, if the schema contains an object, but `style` and `explode` combo is not applicable.
282
- """
283
-
284
- def to_list_of_strings(value: Any) -> list[str]:
285
- if isinstance(value, list):
286
- return list(map(str, value))
287
- if isinstance(value, str):
288
- return [value]
289
- return [str(value)]
290
-
291
- return {key: to_list_of_strings(value) for key, value in (query or {}).items()}
@@ -11,6 +11,7 @@ from ... import experimental, fixups
11
11
  from ...code_samples import CodeSampleStyle
12
12
  from ...constants import NOT_SET, WAIT_FOR_SCHEMA_INTERVAL
13
13
  from ...exceptions import SchemaError, SchemaErrorType
14
+ from ...filters import filter_set_from_components
14
15
  from ...generation import (
15
16
  DEFAULT_DATA_GENERATION_METHODS,
16
17
  DataGenerationMethod,
@@ -330,17 +331,22 @@ def from_dict(
330
331
  if rate_limit is not None:
331
332
  rate_limiter = build_limiter(rate_limit)
332
333
 
334
+ filter_set = filter_set_from_components(
335
+ include=True,
336
+ method=method,
337
+ endpoint=endpoint,
338
+ tag=tag,
339
+ operation_id=operation_id,
340
+ skip_deprecated_operations=skip_deprecated_operations,
341
+ )
342
+
333
343
  def init_openapi_2() -> SwaggerV20:
334
344
  _maybe_validate_schema(raw_schema, definitions.SWAGGER_20_VALIDATOR, validate_schema)
335
345
  instance = SwaggerV20(
336
346
  raw_schema,
337
347
  app=app,
338
348
  base_url=base_url,
339
- method=method,
340
- endpoint=endpoint,
341
- tag=tag,
342
- operation_id=operation_id,
343
- skip_deprecated_operations=skip_deprecated_operations,
349
+ filter_set=filter_set,
344
350
  validate_schema=validate_schema,
345
351
  data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
346
352
  generation_config=generation_config or GenerationConfig(),
@@ -379,11 +385,7 @@ def from_dict(
379
385
  raw_schema,
380
386
  app=app,
381
387
  base_url=base_url,
382
- method=method,
383
- endpoint=endpoint,
384
- tag=tag,
385
- operation_id=operation_id,
386
- skip_deprecated_operations=skip_deprecated_operations,
388
+ filter_set=filter_set,
387
389
  validate_schema=validate_schema,
388
390
  data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
389
391
  generation_config=generation_config or GenerationConfig(),
@@ -8,6 +8,7 @@ from difflib import get_close_matches
8
8
  from hashlib import sha1
9
9
  from json import JSONDecodeError
10
10
  from threading import RLock
11
+ from types import SimpleNamespace
11
12
  from typing import (
12
13
  TYPE_CHECKING,
13
14
  Any,
@@ -63,13 +64,6 @@ from ._hypothesis import get_case_strategy
63
64
  from .converter import to_json_schema, to_json_schema_recursive
64
65
  from .definitions import OPENAPI_30_VALIDATOR, OPENAPI_31_VALIDATOR, SWAGGER_20_VALIDATOR
65
66
  from .examples import get_strategies_from_examples
66
- from .filters import (
67
- should_skip_by_operation_id,
68
- should_skip_by_tag,
69
- should_skip_deprecated,
70
- should_skip_endpoint,
71
- should_skip_method,
72
- )
73
67
  from .parameters import (
74
68
  OpenAPI20Body,
75
69
  OpenAPI20CompositeBody,
@@ -149,14 +143,32 @@ class BaseOpenAPISchema(BaseSchema):
149
143
  message += f". Did you mean `{matches[0]}`?"
150
144
  raise OperationNotFound(message=message, item=item) from exc
151
145
 
152
- def _should_skip(self, method: str, definition: dict[str, Any]) -> bool:
153
- return (
154
- method not in HTTP_METHODS
155
- or should_skip_method(method, self.method)
156
- or should_skip_deprecated(definition.get("deprecated", False), self.skip_deprecated_operations)
157
- or should_skip_by_tag(definition.get("tags"), self.tag)
158
- or should_skip_by_operation_id(definition.get("operationId"), self.operation_id)
159
- )
146
+ def _should_skip(
147
+ self,
148
+ path: str,
149
+ method: str,
150
+ definition: dict[str, Any],
151
+ _ctx_cache: SimpleNamespace = SimpleNamespace(
152
+ operation=APIOperation(
153
+ method="",
154
+ path="",
155
+ verbose_name="",
156
+ definition=OperationDefinition(raw=None, resolved=None, scope=""),
157
+ schema=None, # type: ignore
158
+ )
159
+ ),
160
+ ) -> bool:
161
+ if method not in HTTP_METHODS:
162
+ return True
163
+ # Attribute assignment is way faster than creating a new namespace every time
164
+ operation = _ctx_cache.operation
165
+ operation.method = method
166
+ operation.path = path
167
+ operation.verbose_name = f"{method.upper()} {path}"
168
+ operation.definition.raw = definition
169
+ operation.definition.resolved = definition
170
+ operation.schema = self
171
+ return not self.filter_set.match(_ctx_cache)
160
172
 
161
173
  def _operation_iter(self) -> Generator[dict[str, Any], None, None]:
162
174
  try:
@@ -164,19 +176,16 @@ class BaseOpenAPISchema(BaseSchema):
164
176
  except KeyError:
165
177
  return
166
178
  get_full_path = self.get_full_path
167
- endpoint = self.endpoint
168
179
  resolve = self.resolver.resolve
169
180
  should_skip = self._should_skip
170
181
  for path, path_item in paths.items():
171
182
  full_path = get_full_path(path)
172
- if should_skip_endpoint(full_path, endpoint):
173
- continue
174
183
  try:
175
184
  if "$ref" in path_item:
176
185
  _, path_item = resolve(path_item["$ref"])
177
186
  # Straightforward iteration is faster than converting to a set & calculating length.
178
187
  for method, definition in path_item.items():
179
- if should_skip(method, definition):
188
+ if should_skip(full_path, method, definition):
180
189
  continue
181
190
  yield definition
182
191
  except SCHEMA_PARSING_ERRORS:
@@ -274,7 +283,6 @@ class BaseOpenAPISchema(BaseSchema):
274
283
  context = HookContext()
275
284
  # Optimization: local variables are faster than attribute access
276
285
  get_full_path = self.get_full_path
277
- endpoint = self.endpoint
278
286
  dispatch_hook = self.dispatch_hook
279
287
  resolve_path_item = self._resolve_path_item
280
288
  resolve_shared_parameters = self._resolve_shared_parameters
@@ -287,8 +295,6 @@ class BaseOpenAPISchema(BaseSchema):
287
295
  method = None
288
296
  try:
289
297
  full_path = get_full_path(path) # Should be available for later use
290
- if should_skip_endpoint(full_path, endpoint):
291
- continue
292
298
  dispatch_hook("before_process_path", context, path, path_item)
293
299
  scope, path_item = resolve_path_item(path_item)
294
300
  with in_scope(self.resolver, scope):
@@ -298,7 +304,7 @@ class BaseOpenAPISchema(BaseSchema):
298
304
  continue
299
305
  try:
300
306
  resolved = resolve_operation(entry)
301
- if should_skip(method, resolved):
307
+ if should_skip(full_path, method, resolved):
302
308
  continue
303
309
  parameters = resolved.get("parameters", ())
304
310
  parameters = collect_parameters(itertools.chain(parameters, shared_parameters), resolved)
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
- from dataclasses import dataclass
4
+ from dataclasses import asdict as _asdict, dataclass
5
5
  from enum import Enum
6
- from typing import TYPE_CHECKING, Type
6
+ from typing import TYPE_CHECKING, Type, Any
7
+
8
+ from ..exceptions import format_exception
7
9
 
8
10
  if TYPE_CHECKING:
9
11
  from ..models import Case, Check
@@ -28,6 +30,9 @@ class StatefulEvent:
28
30
 
29
31
  __slots__ = ("timestamp",)
30
32
 
33
+ def asdict(self) -> dict[str, Any]:
34
+ return _asdict(self)
35
+
31
36
 
32
37
  @dataclass
33
38
  class RunStarted(StatefulEvent):
@@ -43,6 +48,12 @@ class RunStarted(StatefulEvent):
43
48
  self.started_at = time.time()
44
49
  self.timestamp = time.monotonic()
45
50
 
51
+ def asdict(self) -> dict[str, Any]:
52
+ return {
53
+ "timestamp": self.timestamp,
54
+ "started_at": self.started_at,
55
+ }
56
+
46
57
 
47
58
  @dataclass
48
59
  class RunFinished(StatefulEvent):
@@ -90,6 +101,15 @@ class SuiteFinished(StatefulEvent):
90
101
  self.failures = failures
91
102
  self.timestamp = time.monotonic()
92
103
 
104
+ def asdict(self) -> dict[str, Any]:
105
+ from ..runner.serialization import SerializedCheck, _serialize_check
106
+
107
+ return {
108
+ "timestamp": self.timestamp,
109
+ "status": self.status,
110
+ "failures": [_serialize_check(SerializedCheck.from_check(failure)) for failure in self.failures],
111
+ }
112
+
93
113
 
94
114
  class ScenarioStatus(str, Enum):
95
115
  """Status of a single scenario execution."""
@@ -203,6 +223,26 @@ class StepFinished(StatefulEvent):
203
223
  self.checks = checks
204
224
  self.timestamp = time.monotonic()
205
225
 
226
+ def asdict(self) -> dict[str, Any]:
227
+ return {
228
+ "timestamp": self.timestamp,
229
+ "status": self.status,
230
+ "transition_id": {
231
+ "name": self.transition_id.name,
232
+ "status_code": self.transition_id.status_code,
233
+ "source": self.transition_id.source,
234
+ }
235
+ if self.transition_id is not None
236
+ else None,
237
+ "target": self.target,
238
+ "response": {
239
+ "status_code": self.response.status_code,
240
+ "elapsed": self.response.elapsed.total_seconds(),
241
+ }
242
+ if self.response is not None
243
+ else None,
244
+ }
245
+
206
246
 
207
247
  @dataclass
208
248
  class Interrupted(StatefulEvent):
@@ -225,3 +265,9 @@ class Errored(StatefulEvent):
225
265
  def __init__(self, *, exception: Exception) -> None:
226
266
  self.exception = exception
227
267
  self.timestamp = time.monotonic()
268
+
269
+ def asdict(self) -> dict[str, Any]:
270
+ return {
271
+ "timestamp": self.timestamp,
272
+ "exception": format_exception(self.exception, True),
273
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: schemathesis
3
- Version: 3.32.0
3
+ Version: 3.32.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
@@ -12,16 +12,16 @@ schemathesis/code_samples.py,sha256=xk1-1jnXg5hS40VzIZp8PEtZwGaazNlVKMT7_X-zG-M,
12
12
  schemathesis/constants.py,sha256=l1YQ7PXhEj9dyf9CTESVUpPOaFCH7iz-Fe8o4v6Th_s,2673
13
13
  schemathesis/exceptions.py,sha256=w-5A-2Yb6EJOSvPycbx79T9Lr1IcMWJ8UFq6DW9kIMc,19975
14
14
  schemathesis/failures.py,sha256=wXz5Kr5i-ojcYc-BdzFlNbNGOfoVXHZM6kd4iULdHK4,7003
15
- schemathesis/filters.py,sha256=0fYzn9sJ35k3Znx1P8FrbSdoUcdslcibtGh-IOTRwB8,10251
15
+ schemathesis/filters.py,sha256=02mg5Gn32AkOprfk33wjH8oXmbsYpogN8XGlrHkEo8Q,14265
16
16
  schemathesis/graphql.py,sha256=YkoKWY5K8lxp7H3ikAs-IsoDbiPwJvChG7O8p3DgwtI,229
17
17
  schemathesis/hooks.py,sha256=dveqMmThIvt4fDahUXhU2nCq5pFvYjzzd1Ys_MhrJZA,12398
18
- schemathesis/lazy.py,sha256=eVdGkTZK0fWvUlFUCFGGlViH2NWEtYIjxiNkF4fBWhI,15218
18
+ schemathesis/lazy.py,sha256=yEMQXbve2cB5mX-9Kv9npUECYfXVMc-dK33k82WJVcM,15405
19
19
  schemathesis/loaders.py,sha256=OtCD1o0TVmSNAUF7dgHpouoAXtY6w9vEtsRVGv4lE0g,4588
20
20
  schemathesis/models.py,sha256=nbm9Agqw94RYib2Q4OH7iOiEPDGw64NlQnEB7o5Spio,44925
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=nUJIfR0mXYpKwEX3mZiS3hpUDdkD3vulROVeovL0dqU,18331
24
+ schemathesis/schemas.py,sha256=wfcZflxe6ITt6s1-d37RwoRHJaYckWS76T-_PBCC7hI,17757
25
25
  schemathesis/serializers.py,sha256=kxXZ-UGa1v_vOm0sC4QYcrNv4rfvI7tHGT2elRVbCbc,11649
26
26
  schemathesis/targets.py,sha256=XIGRghvEzbmEJjse9aZgNEj67L3jAbiazm2rxURWgDE,2351
27
27
  schemathesis/throttling.py,sha256=aisUc4MJDGIOGUAs9L2DlWWpdd4KyAFuNVKhYoaUC9M,1719
@@ -69,9 +69,9 @@ schemathesis/internal/result.py,sha256=d449YvyONjqjDs-A5DAPgtAI96iT753K8sU6_1HLo
69
69
  schemathesis/internal/transformation.py,sha256=3S6AzAqdsEsB5iobFgSuvL0UMUqH0JHC7hGxKwcpqPw,450
70
70
  schemathesis/internal/validation.py,sha256=G7i8jIMUpAeOnDsDF_eWYvRZe_yMprRswx0QAtMPyEw,966
71
71
  schemathesis/runner/__init__.py,sha256=mXFJTAjbjfGImIOB5d1rkpZC5TtVRxSf_SMiBEhKNMI,21350
72
- schemathesis/runner/events.py,sha256=VVFy-qAgxoHBNrexBakq_QX_hx52pnWK-67HgSPpYRw,10694
72
+ schemathesis/runner/events.py,sha256=ljwDjv-4hLS4MPfdfYDB2EAxkJR_67-fVvMu-pJU-yk,11498
73
73
  schemathesis/runner/probes.py,sha256=no5AfO3kse25qvHevjeUfB0Q3C860V2AYzschUW3QMQ,5688
74
- schemathesis/runner/serialization.py,sha256=sEUhNBGmTGMidaJEQt66tF5LEbwwxciT-ldDsZpSkL8,17522
74
+ schemathesis/runner/serialization.py,sha256=erbXEHyI8rIlkQ42AwgmlH7aAbh313EPqCEfrGKxUls,20040
75
75
  schemathesis/runner/impl/__init__.py,sha256=1E2iME8uthYPBh9MjwVBCTFV-P3fi7AdphCCoBBspjs,199
76
76
  schemathesis/runner/impl/core.py,sha256=k1c2QMMoqtXk1bafVRxJwGnIxTQxU0Pr1Am0Xo9WPd4,45063
77
77
  schemathesis/runner/impl/solo.py,sha256=N7-pUL6nWGiSRUC4Zqy1T4h99vbeQowP6b6cMnobOow,3042
@@ -87,7 +87,7 @@ schemathesis/service/hosts.py,sha256=ad2Lxq9Zcc9PP-1eFLQnxen4ImglcGOH8n7CGG72NNg
87
87
  schemathesis/service/metadata.py,sha256=x2LeCED1mdPf-YQJmjY8xtcIKHfD1ap5V0BGl-UgqNo,2087
88
88
  schemathesis/service/models.py,sha256=ihItUJ9CvH4TvmdfJY3W88NR82OODF8a3RD7WRXn6RM,6578
89
89
  schemathesis/service/report.py,sha256=4A8nf6_KOjDW3x1VXF8gSf_WY2xXp1Cbz-Owl_GeR7o,8294
90
- schemathesis/service/serialization.py,sha256=GFNc1-w1ShZopFdR7OFLT9ZKe8Y2S1hYnlXMrdMj3VE,10600
90
+ schemathesis/service/serialization.py,sha256=QX7-wbh3vvUrZtRQWZkwaIUSIkcPtD-RMNFmHBY8J5s,6196
91
91
  schemathesis/service/usage.py,sha256=UbXqxeDq5mAjKkfV4hApZsReZmQHXiqoXUYn_Z6YuZk,2438
92
92
  schemathesis/specs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
93
93
  schemathesis/specs/graphql/__init__.py,sha256=fgyHtvWNUVWismBTOqxQtgLoTighTfvMv6v6QCD_Oyc,85
@@ -105,14 +105,13 @@ schemathesis/specs/openapi/constants.py,sha256=JqM_FHOenqS_MuUE9sxVQ8Hnw0DNM8cnK
105
105
  schemathesis/specs/openapi/converter.py,sha256=TaYgc5BBHPdkN-n0lqpbeVgLu3eL3L8Wu3y_Vo3TJaQ,2800
106
106
  schemathesis/specs/openapi/definitions.py,sha256=Z186F0gNBSCmPg-Kk7Q-n6XxEZHIOzgUyeqixlC62XE,94058
107
107
  schemathesis/specs/openapi/examples.py,sha256=5bjmW3BnJVTiLlWZbimdfOzQQFR6m1P9G0FErr9g3WI,15128
108
- schemathesis/specs/openapi/filters.py,sha256=6Q9eNQ6zCR-NQkUxgnkSDWxfk3hsZuxemBv7v1rhwb4,1437
109
108
  schemathesis/specs/openapi/formats.py,sha256=JmmkQWNAj5XreXb7Edgj4LADAf4m86YulR_Ec8evpJ4,1220
110
109
  schemathesis/specs/openapi/links.py,sha256=2ucOLs50OhCqu0PEdbT_BGUM3fKnHBl97YGISLpAxLY,16023
111
- schemathesis/specs/openapi/loaders.py,sha256=JJdIz1aT03J9WmUWTLOz6Yhuu69IqmhobQ9_vL6XJ6U,24916
110
+ schemathesis/specs/openapi/loaders.py,sha256=NOVG8Jrl9pv5O64wAgWHrco1okpRzQDamHDnidZdIwY,24905
112
111
  schemathesis/specs/openapi/media_types.py,sha256=dNTxpRQbY3SubdVjh4Cjb38R6Bc9MF9BsRQwPD87x0g,1017
113
112
  schemathesis/specs/openapi/parameters.py,sha256=_6vNCnPXcdxjfAQbykCRLHjvmTpu_02xDJghxDrGYr8,13611
114
113
  schemathesis/specs/openapi/references.py,sha256=euxM02kQGMHh4Ss1jWjOY_gyw_HazafKITIsvOEiAvI,9831
115
- schemathesis/specs/openapi/schemas.py,sha256=OF-a5dHthqw3z1X4RcYJqkOoKAsXFKc46RewePNYoBs,52279
114
+ schemathesis/specs/openapi/schemas.py,sha256=EvdW-aJ-OY-zdSU3AbgK1TydsEJueWMfCZRIfAyMUbI,52420
116
115
  schemathesis/specs/openapi/security.py,sha256=nEhDB_SvEFldmfpa9uOQywfWN6DtXHKmgtwucJvfN5Q,7096
117
116
  schemathesis/specs/openapi/serialization.py,sha256=5qGdFHZ3n80UlbSXrO_bkr4Al_7ci_Z3aSUjZczNDQY,11384
118
117
  schemathesis/specs/openapi/utils.py,sha256=-TCu0hTrlwp2x5qHNp-TxiHRMeIZC9OBmlhLssjRIiQ,742
@@ -134,7 +133,7 @@ schemathesis/specs/openapi/stateful/types.py,sha256=UuGcCTFvaHsqeLN9ZeUNcbjsEwmt
134
133
  schemathesis/stateful/__init__.py,sha256=qyQJ-9Ect-AWZiAsK63F3BTGu-jZnPCOp1q46YAonkQ,4911
135
134
  schemathesis/stateful/config.py,sha256=rtGl3egoUuPFxrWcl5xZj_6KmKzZyYaC0b_AUotvurs,2930
136
135
  schemathesis/stateful/context.py,sha256=MeP3-lKyhtAd-jzApC65AWlDZSOBzQq0IgK-nvagYqs,4519
137
- schemathesis/stateful/events.py,sha256=Tj0vaVnhYm2QEw9M_qrj2qbmR4-1xSjpco3L0IXAqvQ,5229
136
+ schemathesis/stateful/events.py,sha256=3AANEwTZFuW9Gbcig70NvdT_h4o5r-Esx9Hpm7zphdg,6710
138
137
  schemathesis/stateful/runner.py,sha256=vFd8Id3zzSQVHBK9UgInBpBT7oyUNagQ0p7ZJnPyHRk,10969
139
138
  schemathesis/stateful/sink.py,sha256=xjsqJYH5WETKh5pDGlchYyjT3HcjzHEotUjvo1p0JsE,2470
140
139
  schemathesis/stateful/state_machine.py,sha256=iRbznWxHnUdLhMpiBaHxe6Nh1EacyGnGFz4DCRwV5j4,12228
@@ -145,8 +144,8 @@ schemathesis/transports/auth.py,sha256=yELjkEkfx4g74hNrd0Db9aFf0xDJDRIwhg2vzKOTZ
145
144
  schemathesis/transports/content_types.py,sha256=VrcRQvF5T_TUjrCyrZcYF2LOwKfs3IrLcMtkVSp1ImI,2189
146
145
  schemathesis/transports/headers.py,sha256=hr_AIDOfUxsJxpHfemIZ_uNG3_vzS_ZeMEKmZjbYiBE,990
147
146
  schemathesis/transports/responses.py,sha256=6-gvVcRK0Ho_lSydUysBNFWoJwZEiEgf6Iv-GWkQGd8,1675
148
- schemathesis-3.32.0.dist-info/METADATA,sha256=vl38Pgh74x0u92WP7xmmgbQ7_2zy4n00secYTo7A5sU,17795
149
- schemathesis-3.32.0.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
150
- schemathesis-3.32.0.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
151
- schemathesis-3.32.0.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
152
- schemathesis-3.32.0.dist-info/RECORD,,
147
+ schemathesis-3.32.1.dist-info/METADATA,sha256=LN3HsIvSIqjk0_UQP7qzHHe9tHceThbE-wu9L15SiF0,17795
148
+ schemathesis-3.32.1.dist-info/WHEEL,sha256=hKi7AIIx6qfnsRbr087vpeJnrVUuDokDHZacPPMW7-Y,87
149
+ schemathesis-3.32.1.dist-info/entry_points.txt,sha256=VHyLcOG7co0nOeuk8WjgpRETk5P1E2iCLrn26Zkn5uk,158
150
+ schemathesis-3.32.1.dist-info/licenses/LICENSE,sha256=PsPYgrDhZ7g9uwihJXNG-XVb55wj2uYhkl2DD8oAzY0,1103
151
+ schemathesis-3.32.1.dist-info/RECORD,,
@@ -1,50 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
-
5
- from ...types import Filter
6
-
7
-
8
- def should_skip_method(method: str, pattern: Filter | None) -> bool:
9
- if pattern is None:
10
- return False
11
- patterns = _ensure_tuple(pattern)
12
- return method.upper() not in map(str.upper, patterns)
13
-
14
-
15
- def should_skip_endpoint(endpoint: str, pattern: Filter | None) -> bool:
16
- if pattern is None:
17
- return False
18
- return not _match_any_pattern(endpoint, pattern)
19
-
20
-
21
- def should_skip_by_tag(tags: list[str] | None, pattern: Filter | None) -> bool:
22
- if pattern is None:
23
- return False
24
- if not tags:
25
- return True
26
- patterns = _ensure_tuple(pattern)
27
- return not any(re.search(item, tag) for item in patterns for tag in tags)
28
-
29
-
30
- def should_skip_by_operation_id(operation_id: str | None, pattern: Filter | None) -> bool:
31
- if pattern is None:
32
- return False
33
- if not operation_id:
34
- return True
35
- return not _match_any_pattern(operation_id, pattern)
36
-
37
-
38
- def should_skip_deprecated(is_deprecated: bool, skip_deprecated_operations: bool) -> bool:
39
- return skip_deprecated_operations and is_deprecated
40
-
41
-
42
- def _match_any_pattern(target: str, pattern: Filter) -> bool:
43
- patterns = _ensure_tuple(pattern)
44
- return any(re.search(item, target) for item in patterns)
45
-
46
-
47
- def _ensure_tuple(item: Filter) -> list | set | tuple:
48
- if not isinstance(item, (list, set, tuple)):
49
- return (item,)
50
- return item