schemathesis 3.29.2__py3-none-any.whl → 3.30.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.
Files changed (125) hide show
  1. schemathesis/__init__.py +3 -3
  2. schemathesis/_compat.py +2 -2
  3. schemathesis/_dependency_versions.py +1 -3
  4. schemathesis/_hypothesis.py +6 -0
  5. schemathesis/_lazy_import.py +1 -0
  6. schemathesis/_override.py +1 -0
  7. schemathesis/_rate_limiter.py +2 -1
  8. schemathesis/_xml.py +1 -0
  9. schemathesis/auths.py +4 -2
  10. schemathesis/checks.py +8 -5
  11. schemathesis/cli/__init__.py +28 -1
  12. schemathesis/cli/callbacks.py +3 -4
  13. schemathesis/cli/cassettes.py +6 -4
  14. schemathesis/cli/constants.py +2 -0
  15. schemathesis/cli/context.py +5 -0
  16. schemathesis/cli/debug.py +2 -1
  17. schemathesis/cli/handlers.py +1 -1
  18. schemathesis/cli/junitxml.py +5 -4
  19. schemathesis/cli/options.py +1 -0
  20. schemathesis/cli/output/default.py +56 -24
  21. schemathesis/cli/output/short.py +21 -10
  22. schemathesis/cli/sanitization.py +1 -0
  23. schemathesis/code_samples.py +1 -0
  24. schemathesis/constants.py +1 -0
  25. schemathesis/contrib/openapi/__init__.py +1 -1
  26. schemathesis/contrib/openapi/fill_missing_examples.py +2 -0
  27. schemathesis/contrib/openapi/formats/uuid.py +2 -1
  28. schemathesis/contrib/unique_data.py +2 -1
  29. schemathesis/exceptions.py +42 -61
  30. schemathesis/experimental/__init__.py +14 -0
  31. schemathesis/extra/_aiohttp.py +1 -0
  32. schemathesis/extra/_server.py +1 -0
  33. schemathesis/extra/pytest_plugin.py +13 -24
  34. schemathesis/failures.py +42 -8
  35. schemathesis/filters.py +2 -1
  36. schemathesis/fixups/__init__.py +1 -0
  37. schemathesis/fixups/fast_api.py +2 -2
  38. schemathesis/fixups/utf8_bom.py +1 -2
  39. schemathesis/generation/__init__.py +2 -1
  40. schemathesis/hooks.py +3 -1
  41. schemathesis/internal/copy.py +19 -3
  42. schemathesis/internal/deprecation.py +1 -1
  43. schemathesis/internal/jsonschema.py +2 -1
  44. schemathesis/internal/output.py +68 -0
  45. schemathesis/internal/result.py +1 -1
  46. schemathesis/internal/transformation.py +1 -0
  47. schemathesis/lazy.py +11 -2
  48. schemathesis/loaders.py +4 -2
  49. schemathesis/models.py +22 -7
  50. schemathesis/parameters.py +1 -0
  51. schemathesis/runner/__init__.py +1 -1
  52. schemathesis/runner/events.py +22 -4
  53. schemathesis/runner/impl/core.py +69 -33
  54. schemathesis/runner/impl/solo.py +2 -1
  55. schemathesis/runner/impl/threadpool.py +4 -0
  56. schemathesis/runner/probes.py +1 -1
  57. schemathesis/runner/serialization.py +1 -1
  58. schemathesis/sanitization.py +2 -0
  59. schemathesis/schemas.py +7 -4
  60. schemathesis/service/ci.py +1 -0
  61. schemathesis/service/client.py +7 -7
  62. schemathesis/service/events.py +2 -1
  63. schemathesis/service/extensions.py +5 -5
  64. schemathesis/service/hosts.py +1 -0
  65. schemathesis/service/metadata.py +2 -1
  66. schemathesis/service/models.py +2 -1
  67. schemathesis/service/report.py +3 -3
  68. schemathesis/service/serialization.py +62 -23
  69. schemathesis/service/usage.py +1 -0
  70. schemathesis/specs/graphql/_cache.py +1 -1
  71. schemathesis/specs/graphql/loaders.py +17 -1
  72. schemathesis/specs/graphql/nodes.py +1 -0
  73. schemathesis/specs/graphql/scalars.py +2 -2
  74. schemathesis/specs/graphql/schemas.py +7 -7
  75. schemathesis/specs/graphql/validation.py +1 -2
  76. schemathesis/specs/openapi/_hypothesis.py +17 -11
  77. schemathesis/specs/openapi/checks.py +102 -9
  78. schemathesis/specs/openapi/converter.py +2 -1
  79. schemathesis/specs/openapi/definitions.py +2 -1
  80. schemathesis/specs/openapi/examples.py +7 -9
  81. schemathesis/specs/openapi/expressions/__init__.py +29 -2
  82. schemathesis/specs/openapi/expressions/context.py +1 -1
  83. schemathesis/specs/openapi/expressions/extractors.py +23 -0
  84. schemathesis/specs/openapi/expressions/lexer.py +19 -18
  85. schemathesis/specs/openapi/expressions/nodes.py +24 -4
  86. schemathesis/specs/openapi/expressions/parser.py +26 -5
  87. schemathesis/specs/openapi/filters.py +1 -0
  88. schemathesis/specs/openapi/links.py +35 -7
  89. schemathesis/specs/openapi/loaders.py +31 -11
  90. schemathesis/specs/openapi/negative/__init__.py +2 -1
  91. schemathesis/specs/openapi/negative/mutations.py +1 -0
  92. schemathesis/specs/openapi/parameters.py +1 -0
  93. schemathesis/specs/openapi/schemas.py +28 -39
  94. schemathesis/specs/openapi/security.py +1 -0
  95. schemathesis/specs/openapi/serialization.py +1 -0
  96. schemathesis/specs/openapi/stateful/__init__.py +159 -70
  97. schemathesis/specs/openapi/stateful/statistic.py +198 -0
  98. schemathesis/specs/openapi/stateful/types.py +13 -0
  99. schemathesis/specs/openapi/utils.py +1 -0
  100. schemathesis/specs/openapi/validation.py +1 -0
  101. schemathesis/stateful/__init__.py +4 -2
  102. schemathesis/stateful/config.py +66 -0
  103. schemathesis/stateful/context.py +103 -0
  104. schemathesis/stateful/events.py +215 -0
  105. schemathesis/stateful/runner.py +238 -0
  106. schemathesis/stateful/sink.py +68 -0
  107. schemathesis/stateful/state_machine.py +39 -22
  108. schemathesis/stateful/statistic.py +20 -0
  109. schemathesis/stateful/validation.py +66 -0
  110. schemathesis/targets.py +1 -0
  111. schemathesis/throttling.py +23 -3
  112. schemathesis/transports/__init__.py +28 -10
  113. schemathesis/transports/auth.py +1 -0
  114. schemathesis/transports/content_types.py +1 -1
  115. schemathesis/transports/headers.py +2 -1
  116. schemathesis/transports/responses.py +6 -4
  117. schemathesis/types.py +1 -0
  118. schemathesis/utils.py +1 -0
  119. {schemathesis-3.29.2.dist-info → schemathesis-3.30.1.dist-info}/METADATA +3 -3
  120. schemathesis-3.30.1.dist-info/RECORD +151 -0
  121. schemathesis/specs/openapi/stateful/links.py +0 -92
  122. schemathesis-3.29.2.dist-info/RECORD +0 -141
  123. {schemathesis-3.29.2.dist-info → schemathesis-3.30.1.dist-info}/WHEEL +0 -0
  124. {schemathesis-3.29.2.dist-info → schemathesis-3.30.1.dist-info}/entry_points.txt +0 -0
  125. {schemathesis-3.29.2.dist-info → schemathesis-3.30.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,10 @@
1
1
  from __future__ import annotations
2
- import json
2
+
3
3
  import hashlib
4
4
  import http
5
+ import json
5
6
  from dataclasses import asdict
6
- from typing import Any, TYPE_CHECKING
7
+ from typing import TYPE_CHECKING, Any
7
8
  from urllib.parse import urljoin
8
9
 
9
10
  import requests
@@ -14,19 +15,18 @@ from .ci import CIProvider
14
15
  from .constants import CI_PROVIDER_HEADER, REPORT_CORRELATION_ID_HEADER, REQUEST_TIMEOUT, UPLOAD_SOURCE_HEADER
15
16
  from .metadata import Metadata, collect_dependency_versions
16
17
  from .models import (
17
- AnalysisSuccess,
18
18
  AnalysisError,
19
19
  AnalysisResult,
20
- ProjectDetails,
20
+ AnalysisSuccess,
21
21
  AuthResponse,
22
22
  FailedUploadResponse,
23
- UploadResponse,
24
- UploadSource,
23
+ ProjectDetails,
25
24
  ProjectEnvironment,
26
25
  Specification,
26
+ UploadResponse,
27
+ UploadSource,
27
28
  )
28
29
 
29
-
30
30
  if TYPE_CHECKING:
31
31
  from ..runner import probes
32
32
 
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
+
2
3
  from dataclasses import dataclass
3
4
 
4
- from . import ci
5
5
  from ..exceptions import format_exception
6
+ from . import ci
6
7
 
7
8
 
8
9
  class Event:
@@ -3,17 +3,17 @@ from __future__ import annotations
3
3
  import base64
4
4
  import re
5
5
  from ipaddress import IPv4Network, IPv6Network
6
- from typing import TYPE_CHECKING, Callable, Optional, Any
6
+ from typing import TYPE_CHECKING, Any, Callable, Optional
7
7
 
8
8
  from ..graphql import nodes
9
- from ..internal.result import Result, Ok, Err
9
+ from ..internal.result import Err, Ok, Result
10
10
  from .models import (
11
11
  Extension,
12
- SchemaPatchesExtension,
13
- StrategyDefinition,
14
- OpenApiStringFormatsExtension,
15
12
  GraphQLScalarsExtension,
16
13
  MediaTypesExtension,
14
+ OpenApiStringFormatsExtension,
15
+ SchemaPatchesExtension,
16
+ StrategyDefinition,
17
17
  TransformFunctionDefinition,
18
18
  )
19
19
 
@@ -1,6 +1,7 @@
1
1
  """Work with stored auth data."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  import enum
5
6
  import tempfile
6
7
  from dataclasses import dataclass
@@ -1,10 +1,11 @@
1
1
  """Useful info to collect from CLI usage."""
2
2
 
3
3
  from __future__ import annotations
4
+
4
5
  import os
5
6
  import platform
6
- from importlib import metadata
7
7
  from dataclasses import dataclass, field
8
+ from importlib import metadata
8
9
 
9
10
  from ..constants import SCHEMATHESIS_VERSION
10
11
  from .constants import DOCKER_IMAGE_ENV_VAR
@@ -1,7 +1,8 @@
1
1
  from __future__ import annotations
2
+
2
3
  from dataclasses import dataclass, field
3
4
  from enum import Enum
4
- from typing import Any, Iterable, TypedDict, Union, Literal
5
+ from typing import Any, Iterable, Literal, TypedDict, Union
5
6
 
6
7
 
7
8
  class UploadSource(str, Enum):
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  import enum
3
4
  import json
4
5
  import os
@@ -9,7 +10,7 @@ from contextlib import suppress
9
10
  from dataclasses import asdict, dataclass, field
10
11
  from io import BytesIO
11
12
  from queue import Queue
12
- from typing import Any, TYPE_CHECKING
13
+ from typing import TYPE_CHECKING, Any
13
14
 
14
15
  import click
15
16
 
@@ -22,11 +23,10 @@ from .metadata import Metadata
22
23
  from .models import UploadResponse
23
24
  from .serialization import serialize_event
24
25
 
25
-
26
26
  if TYPE_CHECKING:
27
- from .client import ServiceClient
28
27
  from ..cli.context import ExecutionContext
29
28
  from ..runner.events import ExecutionEvent
29
+ from .client import ServiceClient
30
30
 
31
31
 
32
32
  @dataclass
@@ -7,9 +7,10 @@ from ..exceptions import format_exception
7
7
  from ..internal.result import Err, Ok
8
8
  from ..internal.transformation import merge_recursively
9
9
  from ..models import Response
10
- from .models import AnalysisSuccess
11
10
  from ..runner import events
12
- from ..runner.serialization import SerializedCase
11
+ from ..runner.serialization import SerializedCase, SerializedCheck
12
+ from ..stateful import events as stateful_events
13
+ from .models import AnalysisSuccess
13
14
 
14
15
  S = TypeVar("S", bound=events.ExecutionEvent)
15
16
  SerializeFunc = Callable[[S], Optional[Dict[str, Any]]]
@@ -81,6 +82,27 @@ def _serialize_response(response: Response) -> dict[str, Any]:
81
82
  }
82
83
 
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
+
84
106
  def serialize_after_execution(event: events.AfterExecution) -> dict[str, Any] | None:
85
107
  return {
86
108
  "correlation_id": event.correlation_id,
@@ -89,27 +111,7 @@ def serialize_after_execution(event: events.AfterExecution) -> dict[str, Any] |
89
111
  "elapsed_time": event.elapsed_time,
90
112
  "data_generation_method": event.data_generation_method,
91
113
  "result": {
92
- "checks": [
93
- {
94
- "name": check.name,
95
- "value": check.value,
96
- "request": {
97
- "method": check.request.method,
98
- "uri": check.request.uri,
99
- "body": check.request.body,
100
- "headers": check.request.headers,
101
- },
102
- "response": _serialize_response(check.response) if check.response is not None else None,
103
- "example": _serialize_case(check.example),
104
- "message": check.message,
105
- "context": asdict(check.context) if check.context is not None else None, # type: ignore
106
- "history": [
107
- {"case": _serialize_case(entry.case), "response": _serialize_response(entry.response)}
108
- for entry in check.history
109
- ],
110
- }
111
- for check in event.result.checks
112
- ],
114
+ "checks": [_serialize_check(check) for check in event.result.checks],
113
115
  "errors": [asdict(error) for error in event.result.errors],
114
116
  "skip_reason": event.result.skip_reason,
115
117
  },
@@ -147,6 +149,37 @@ def serialize_finished(event: events.Finished) -> dict[str, Any] | None:
147
149
  }
148
150
 
149
151
 
152
+ def serialize_stateful_event(event: events.StatefulEvent) -> dict[str, Any] | None:
153
+ return _serialize_stateful_event(event.data)
154
+
155
+
156
+ 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
+ else:
175
+ data = asdict(event)
176
+ return {"data": {event.__class__.__name__: data}}
177
+
178
+
179
+ def serialize_after_stateful_execution(event: events.AfterStatefulExecution) -> dict[str, Any] | None:
180
+ return {"result": asdict(event.result)}
181
+
182
+
150
183
  SERIALIZER_MAP = {
151
184
  events.Initialized: serialize_initialized,
152
185
  events.BeforeProbing: serialize_before_probing,
@@ -157,6 +190,8 @@ SERIALIZER_MAP = {
157
190
  events.AfterExecution: serialize_after_execution,
158
191
  events.Interrupted: serialize_interrupted,
159
192
  events.InternalError: serialize_internal_error,
193
+ events.StatefulEvent: serialize_stateful_event,
194
+ events.AfterStatefulExecution: serialize_after_stateful_execution,
160
195
  events.Finished: serialize_finished,
161
196
  }
162
197
 
@@ -173,6 +208,8 @@ def serialize_event(
173
208
  on_after_execution: SerializeFunc | None = None,
174
209
  on_interrupted: SerializeFunc | None = None,
175
210
  on_internal_error: SerializeFunc | None = None,
211
+ on_stateful_event: SerializeFunc | None = None,
212
+ on_after_stateful_execution: SerializeFunc | None = None,
176
213
  on_finished: SerializeFunc | None = None,
177
214
  extra: dict[str, Any] | None = None,
178
215
  ) -> dict[str, dict[str, Any] | None]:
@@ -188,6 +225,8 @@ def serialize_event(
188
225
  events.AfterExecution: on_after_execution,
189
226
  events.Interrupted: on_interrupted,
190
227
  events.InternalError: on_internal_error,
228
+ events.StatefulEvent: on_stateful_event,
229
+ events.AfterStatefulExecution: on_after_stateful_execution,
191
230
  events.Finished: on_finished,
192
231
  }.get(event.__class__)
193
232
  if serializer is None:
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  import sys
3
4
  from typing import Any
4
5
 
@@ -4,8 +4,8 @@ from dataclasses import dataclass, field
4
4
  from typing import TYPE_CHECKING
5
5
 
6
6
  if TYPE_CHECKING:
7
- from ...schemas import APIOperationMap
8
7
  from ...models import APIOperation
8
+ from ...schemas import APIOperationMap
9
9
 
10
10
 
11
11
  @dataclass
@@ -16,6 +16,7 @@ from ...generation import (
16
16
  GenerationConfig,
17
17
  )
18
18
  from ...hooks import HookContext, dispatch
19
+ from ...internal.output import OutputConfig
19
20
  from ...internal.validation import require_relative_url
20
21
  from ...loaders import load_schema_from_url
21
22
  from ...throttling import build_limiter
@@ -52,6 +53,7 @@ def from_path(
52
53
  base_url: str | None = None,
53
54
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
54
55
  generation_config: GenerationConfig | None = None,
56
+ output_config: OutputConfig | None = None,
55
57
  code_sample_style: str = CodeSampleStyle.default().name,
56
58
  rate_limit: str | None = None,
57
59
  encoding: str = "utf8",
@@ -69,6 +71,8 @@ def from_path(
69
71
  base_url=base_url,
70
72
  data_generation_methods=data_generation_methods,
71
73
  code_sample_style=code_sample_style,
74
+ generation_config=generation_config,
75
+ output_config=output_config,
72
76
  location=pathlib.Path(path).absolute().as_uri(),
73
77
  rate_limit=rate_limit,
74
78
  sanitize_output=sanitize_output,
@@ -161,6 +165,7 @@ def from_file(
161
165
  base_url: str | None = None,
162
166
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
163
167
  generation_config: GenerationConfig | None = None,
168
+ output_config: OutputConfig | None = None,
164
169
  code_sample_style: str = CodeSampleStyle.default().name,
165
170
  location: str | None = None,
166
171
  rate_limit: str | None = None,
@@ -198,6 +203,8 @@ def from_file(
198
203
  app=app,
199
204
  base_url=base_url,
200
205
  data_generation_methods=data_generation_methods,
206
+ generation_config=generation_config,
207
+ output_config=output_config,
201
208
  code_sample_style=code_sample_style,
202
209
  location=location,
203
210
  rate_limit=rate_limit,
@@ -221,6 +228,7 @@ def from_dict(
221
228
  location: str | None = None,
222
229
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
223
230
  generation_config: GenerationConfig | None = None,
231
+ output_config: OutputConfig | None = None,
224
232
  code_sample_style: str = CodeSampleStyle.default().name,
225
233
  rate_limit: str | None = None,
226
234
  sanitize_output: bool = True,
@@ -233,8 +241,8 @@ def from_dict(
233
241
  :param app: A WSGI app instance.
234
242
  :return: GraphQLSchema
235
243
  """
236
- from .schemas import GraphQLSchema
237
244
  from ... import transports
245
+ from .schemas import GraphQLSchema
238
246
 
239
247
  _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
240
248
  hook_context = HookContext()
@@ -250,6 +258,8 @@ def from_dict(
250
258
  base_url=base_url,
251
259
  app=app,
252
260
  data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
261
+ generation_config=generation_config or GenerationConfig(),
262
+ output_config=output_config or OutputConfig(),
253
263
  code_sample_style=_code_sample_style,
254
264
  rate_limiter=rate_limiter,
255
265
  sanitize_output=sanitize_output,
@@ -266,6 +276,7 @@ def from_wsgi(
266
276
  base_url: str | None = None,
267
277
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
268
278
  generation_config: GenerationConfig | None = None,
279
+ output_config: OutputConfig | None = None,
269
280
  code_sample_style: str = CodeSampleStyle.default().name,
270
281
  rate_limit: str | None = None,
271
282
  sanitize_output: bool = True,
@@ -294,6 +305,8 @@ def from_wsgi(
294
305
  base_url=base_url,
295
306
  app=app,
296
307
  data_generation_methods=data_generation_methods,
308
+ generation_config=generation_config,
309
+ output_config=output_config,
297
310
  code_sample_style=code_sample_style,
298
311
  rate_limit=rate_limit,
299
312
  sanitize_output=sanitize_output,
@@ -307,6 +320,7 @@ def from_asgi(
307
320
  base_url: str | None = None,
308
321
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
309
322
  generation_config: GenerationConfig | None = None,
323
+ output_config: OutputConfig | None = None,
310
324
  code_sample_style: str = CodeSampleStyle.default().name,
311
325
  rate_limit: str | None = None,
312
326
  sanitize_output: bool = True,
@@ -332,6 +346,8 @@ def from_asgi(
332
346
  base_url=base_url,
333
347
  app=app,
334
348
  data_generation_methods=data_generation_methods,
349
+ generation_config=generation_config,
350
+ output_config=output_config,
335
351
  code_sample_style=code_sample_style,
336
352
  rate_limit=rate_limit,
337
353
  sanitize_output=sanitize_output,
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  from typing import TYPE_CHECKING
3
4
 
4
5
  if TYPE_CHECKING:
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from functools import lru_cache
4
4
  from typing import TYPE_CHECKING
5
5
 
6
-
7
6
  from ...exceptions import UsageError
8
7
 
9
8
  if TYPE_CHECKING:
@@ -31,9 +30,10 @@ def scalar(name: str, strategy: st.SearchStrategy[graphql.ValueNode]) -> None:
31
30
  @lru_cache
32
31
  def get_extra_scalar_strategies() -> dict[str, st.SearchStrategy]:
33
32
  """Get all extra GraphQL strategies."""
34
- from . import nodes
35
33
  from hypothesis import strategies as st
36
34
 
35
+ from . import nodes
36
+
37
37
  dates = st.dates().map(str)
38
38
  times = st.times().map("%sZ".__mod__)
39
39
 
@@ -6,16 +6,16 @@ from dataclasses import dataclass, field
6
6
  from difflib import get_close_matches
7
7
  from enum import unique
8
8
  from typing import (
9
+ TYPE_CHECKING,
9
10
  Any,
10
11
  Callable,
11
12
  Generator,
13
+ Iterator,
14
+ Mapping,
15
+ NoReturn,
12
16
  Sequence,
13
17
  TypeVar,
14
18
  cast,
15
- TYPE_CHECKING,
16
- NoReturn,
17
- Mapping,
18
- Iterator,
19
19
  )
20
20
  from urllib.parse import urlsplit, urlunsplit
21
21
 
@@ -25,12 +25,11 @@ from hypothesis.strategies import SearchStrategy
25
25
  from hypothesis_graphql import strategies as gql_st
26
26
  from requests.structures import CaseInsensitiveDict
27
27
 
28
- from ..openapi.constants import LOCATION_TO_CONTAINER
29
28
  from ... import auths
30
29
  from ...auths import AuthStorage
31
30
  from ...checks import not_a_server_error
32
31
  from ...constants import NOT_SET
33
- from ...exceptions import OperationSchemaError, OperationNotFound
32
+ from ...exceptions import OperationNotFound, OperationSchemaError
34
33
  from ...generation import DataGenerationMethod, GenerationConfig
35
34
  from ...hooks import (
36
35
  GLOBAL_HOOK_DISPATCHER,
@@ -41,9 +40,10 @@ from ...hooks import (
41
40
  )
42
41
  from ...internal.result import Ok, Result
43
42
  from ...models import APIOperation, Case, CheckFunction, OperationDefinition
44
- from ...schemas import BaseSchema, APIOperationMap
43
+ from ...schemas import APIOperationMap, BaseSchema
45
44
  from ...stateful import Stateful, StatefulTest
46
45
  from ...types import Body, Cookies, Headers, NotSet, PathParameters, Query
46
+ from ..openapi.constants import LOCATION_TO_CONTAINER
47
47
  from ._cache import OperationCache
48
48
  from .scalars import CUSTOM_SCALARS, get_extra_scalar_strategies
49
49
 
@@ -1,5 +1,4 @@
1
- from typing import Any, cast, List
2
-
1
+ from typing import Any, List, cast
3
2
 
4
3
  from ... import failures
5
4
  from ...exceptions import get_grouped_graphql_error, get_unexpected_graphql_response_error
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  import string
3
4
  import time
4
5
  from base64 import b64encode
@@ -9,28 +10,29 @@ from typing import Any, Callable, Dict, Iterable, Optional
9
10
  from urllib.parse import quote_plus
10
11
  from weakref import WeakKeyDictionary
11
12
 
12
- from hypothesis import strategies as st, reject
13
+ from hypothesis import reject
14
+ from hypothesis import strategies as st
13
15
  from hypothesis_jsonschema import from_schema
14
16
  from requests.auth import _basic_auth_str
15
17
  from requests.structures import CaseInsensitiveDict
16
18
  from requests.utils import to_key_val_list
17
19
 
20
+ from ... import auths, serializers
18
21
  from ..._hypothesis import prepare_urlencoded
19
22
  from ...constants import NOT_SET
20
- from .formats import STRING_FORMATS
21
- from ... import auths, serializers
23
+ from ...exceptions import BodyInGetRequestError, SerializationNotPossible
22
24
  from ...generation import DataGenerationMethod, GenerationConfig
23
- from ...internal.copy import fast_deepcopy
24
- from ...exceptions import SerializationNotPossible, BodyInGetRequestError
25
25
  from ...hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
26
+ from ...internal.copy import fast_deepcopy
26
27
  from ...internal.validation import is_illegal_surrogate
27
28
  from ...models import APIOperation, Case, cant_serialize
29
+ from ...serializers import Binary
28
30
  from ...transports.content_types import parse_content_type
29
31
  from ...transports.headers import has_invalid_characters, is_latin_1_encodable
30
32
  from ...types import NotSet
31
- from ...serializers import Binary
32
33
  from ...utils import compose, skip
33
34
  from .constants import LOCATION_TO_CONTAINER
35
+ from .formats import STRING_FORMATS
34
36
  from .media_types import MEDIA_TYPES
35
37
  from .negative import negative_schema
36
38
  from .negative.utils import can_negate
@@ -360,11 +362,15 @@ def get_parameters_strategy(
360
362
  if operation in _PARAMETER_STRATEGIES_CACHE and nested_cache_key in _PARAMETER_STRATEGIES_CACHE[operation]:
361
363
  return _PARAMETER_STRATEGIES_CACHE[operation][nested_cache_key]
362
364
  schema = parameters_to_json_schema(operation, parameters)
363
- if not operation.schema.validate_schema and location == "path":
364
- # If schema validation is disabled, we try to generate data even if the parameter definition
365
- # contains errors.
366
- # In this case, we know that the `required` keyword should always be `True`.
367
- schema["required"] = list(schema["properties"])
365
+ if location == "path":
366
+ if not operation.schema.validate_schema:
367
+ # If schema validation is disabled, we try to generate data even if the parameter definition
368
+ # contains errors.
369
+ # In this case, we know that the `required` keyword should always be `True`.
370
+ schema["required"] = list(schema["properties"])
371
+ for prop in schema.get("properties", {}).values():
372
+ if prop.get("type") == "string":
373
+ prop.setdefault("minLength", 1)
368
374
  schema = operation.schema.prepare_schema(schema)
369
375
  for name in exclude:
370
376
  # Values from `exclude` are not necessarily valid for the schema - they come from user-defined examples