schemathesis 3.25.5__py3-none-any.whl → 3.39.7__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 (146) hide show
  1. schemathesis/__init__.py +6 -6
  2. schemathesis/_compat.py +2 -2
  3. schemathesis/_dependency_versions.py +4 -2
  4. schemathesis/_hypothesis.py +369 -56
  5. schemathesis/_lazy_import.py +1 -0
  6. schemathesis/_override.py +5 -4
  7. schemathesis/_patches.py +21 -0
  8. schemathesis/_rate_limiter.py +7 -0
  9. schemathesis/_xml.py +75 -22
  10. schemathesis/auths.py +78 -16
  11. schemathesis/checks.py +21 -9
  12. schemathesis/cli/__init__.py +793 -448
  13. schemathesis/cli/__main__.py +4 -0
  14. schemathesis/cli/callbacks.py +58 -13
  15. schemathesis/cli/cassettes.py +233 -47
  16. schemathesis/cli/constants.py +8 -2
  17. schemathesis/cli/context.py +24 -4
  18. schemathesis/cli/debug.py +2 -1
  19. schemathesis/cli/handlers.py +4 -1
  20. schemathesis/cli/junitxml.py +103 -22
  21. schemathesis/cli/options.py +15 -4
  22. schemathesis/cli/output/default.py +286 -115
  23. schemathesis/cli/output/short.py +25 -6
  24. schemathesis/cli/reporting.py +79 -0
  25. schemathesis/cli/sanitization.py +6 -0
  26. schemathesis/code_samples.py +5 -3
  27. schemathesis/constants.py +1 -0
  28. schemathesis/contrib/openapi/__init__.py +1 -1
  29. schemathesis/contrib/openapi/fill_missing_examples.py +3 -1
  30. schemathesis/contrib/openapi/formats/uuid.py +2 -1
  31. schemathesis/contrib/unique_data.py +3 -3
  32. schemathesis/exceptions.py +76 -65
  33. schemathesis/experimental/__init__.py +35 -0
  34. schemathesis/extra/_aiohttp.py +1 -0
  35. schemathesis/extra/_flask.py +4 -1
  36. schemathesis/extra/_server.py +1 -0
  37. schemathesis/extra/pytest_plugin.py +17 -25
  38. schemathesis/failures.py +77 -9
  39. schemathesis/filters.py +185 -8
  40. schemathesis/fixups/__init__.py +1 -0
  41. schemathesis/fixups/fast_api.py +2 -2
  42. schemathesis/fixups/utf8_bom.py +1 -2
  43. schemathesis/generation/__init__.py +20 -36
  44. schemathesis/generation/_hypothesis.py +59 -0
  45. schemathesis/generation/_methods.py +44 -0
  46. schemathesis/generation/coverage.py +931 -0
  47. schemathesis/graphql.py +0 -1
  48. schemathesis/hooks.py +89 -12
  49. schemathesis/internal/checks.py +84 -0
  50. schemathesis/internal/copy.py +22 -3
  51. schemathesis/internal/deprecation.py +6 -2
  52. schemathesis/internal/diff.py +15 -0
  53. schemathesis/internal/extensions.py +27 -0
  54. schemathesis/internal/jsonschema.py +2 -1
  55. schemathesis/internal/output.py +68 -0
  56. schemathesis/internal/result.py +1 -1
  57. schemathesis/internal/transformation.py +11 -0
  58. schemathesis/lazy.py +138 -25
  59. schemathesis/loaders.py +7 -5
  60. schemathesis/models.py +323 -213
  61. schemathesis/parameters.py +4 -0
  62. schemathesis/runner/__init__.py +72 -22
  63. schemathesis/runner/events.py +86 -6
  64. schemathesis/runner/impl/context.py +104 -0
  65. schemathesis/runner/impl/core.py +447 -187
  66. schemathesis/runner/impl/solo.py +19 -29
  67. schemathesis/runner/impl/threadpool.py +70 -79
  68. schemathesis/{cli → runner}/probes.py +37 -25
  69. schemathesis/runner/serialization.py +150 -17
  70. schemathesis/sanitization.py +5 -1
  71. schemathesis/schemas.py +170 -102
  72. schemathesis/serializers.py +17 -4
  73. schemathesis/service/ci.py +1 -0
  74. schemathesis/service/client.py +39 -6
  75. schemathesis/service/events.py +5 -1
  76. schemathesis/service/extensions.py +224 -0
  77. schemathesis/service/hosts.py +6 -2
  78. schemathesis/service/metadata.py +25 -0
  79. schemathesis/service/models.py +211 -2
  80. schemathesis/service/report.py +6 -6
  81. schemathesis/service/serialization.py +60 -71
  82. schemathesis/service/usage.py +1 -0
  83. schemathesis/specs/graphql/_cache.py +26 -0
  84. schemathesis/specs/graphql/loaders.py +25 -5
  85. schemathesis/specs/graphql/nodes.py +1 -0
  86. schemathesis/specs/graphql/scalars.py +2 -2
  87. schemathesis/specs/graphql/schemas.py +130 -100
  88. schemathesis/specs/graphql/validation.py +1 -2
  89. schemathesis/specs/openapi/__init__.py +1 -0
  90. schemathesis/specs/openapi/_cache.py +123 -0
  91. schemathesis/specs/openapi/_hypothesis.py +79 -61
  92. schemathesis/specs/openapi/checks.py +504 -25
  93. schemathesis/specs/openapi/converter.py +31 -4
  94. schemathesis/specs/openapi/definitions.py +10 -17
  95. schemathesis/specs/openapi/examples.py +143 -31
  96. schemathesis/specs/openapi/expressions/__init__.py +37 -2
  97. schemathesis/specs/openapi/expressions/context.py +1 -1
  98. schemathesis/specs/openapi/expressions/extractors.py +26 -0
  99. schemathesis/specs/openapi/expressions/lexer.py +20 -18
  100. schemathesis/specs/openapi/expressions/nodes.py +29 -6
  101. schemathesis/specs/openapi/expressions/parser.py +26 -5
  102. schemathesis/specs/openapi/formats.py +44 -0
  103. schemathesis/specs/openapi/links.py +125 -42
  104. schemathesis/specs/openapi/loaders.py +77 -36
  105. schemathesis/specs/openapi/media_types.py +34 -0
  106. schemathesis/specs/openapi/negative/__init__.py +6 -3
  107. schemathesis/specs/openapi/negative/mutations.py +21 -6
  108. schemathesis/specs/openapi/parameters.py +39 -25
  109. schemathesis/specs/openapi/patterns.py +137 -0
  110. schemathesis/specs/openapi/references.py +37 -7
  111. schemathesis/specs/openapi/schemas.py +368 -242
  112. schemathesis/specs/openapi/security.py +25 -7
  113. schemathesis/specs/openapi/serialization.py +1 -0
  114. schemathesis/specs/openapi/stateful/__init__.py +198 -70
  115. schemathesis/specs/openapi/stateful/statistic.py +198 -0
  116. schemathesis/specs/openapi/stateful/types.py +14 -0
  117. schemathesis/specs/openapi/utils.py +6 -1
  118. schemathesis/specs/openapi/validation.py +1 -0
  119. schemathesis/stateful/__init__.py +35 -21
  120. schemathesis/stateful/config.py +97 -0
  121. schemathesis/stateful/context.py +135 -0
  122. schemathesis/stateful/events.py +274 -0
  123. schemathesis/stateful/runner.py +309 -0
  124. schemathesis/stateful/sink.py +68 -0
  125. schemathesis/stateful/state_machine.py +67 -38
  126. schemathesis/stateful/statistic.py +22 -0
  127. schemathesis/stateful/validation.py +100 -0
  128. schemathesis/targets.py +33 -1
  129. schemathesis/throttling.py +25 -5
  130. schemathesis/transports/__init__.py +354 -0
  131. schemathesis/transports/asgi.py +7 -0
  132. schemathesis/transports/auth.py +25 -2
  133. schemathesis/transports/content_types.py +3 -1
  134. schemathesis/transports/headers.py +2 -1
  135. schemathesis/transports/responses.py +9 -4
  136. schemathesis/types.py +9 -0
  137. schemathesis/utils.py +11 -16
  138. schemathesis-3.39.7.dist-info/METADATA +293 -0
  139. schemathesis-3.39.7.dist-info/RECORD +160 -0
  140. {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/WHEEL +1 -1
  141. schemathesis/specs/openapi/filters.py +0 -49
  142. schemathesis/specs/openapi/stateful/links.py +0 -92
  143. schemathesis-3.25.5.dist-info/METADATA +0 -356
  144. schemathesis-3.25.5.dist-info/RECORD +0 -134
  145. {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
  146. {schemathesis-3.25.5.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,14 @@
1
1
  from __future__ import annotations
2
+
2
3
  from dataclasses import asdict
3
- from typing import Any, Callable, Dict, Optional, TypeVar, cast
4
+ from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, TypeVar, cast
4
5
 
5
- from ..models import Response
6
- from ..runner import events
7
- from ..runner.serialization import SerializedCase
8
6
  from ..internal.transformation import merge_recursively
7
+ from ..runner import events
8
+ from ..runner.serialization import _serialize_check
9
+
10
+ if TYPE_CHECKING:
11
+ from ..stateful import events as stateful_events
9
12
 
10
13
  S = TypeVar("S", bound=events.ExecutionEvent)
11
14
  SerializeFunc = Callable[[S], Optional[Dict[str, Any]]]
@@ -19,32 +22,28 @@ def serialize_initialized(event: events.Initialized) -> dict[str, Any] | None:
19
22
  }
20
23
 
21
24
 
22
- def serialize_before_execution(event: events.BeforeExecution) -> dict[str, Any] | None:
23
- return {
24
- "correlation_id": event.correlation_id,
25
- "verbose_name": event.verbose_name,
26
- "data_generation_method": event.data_generation_method,
27
- }
25
+ def serialize_before_probing(_: events.BeforeProbing) -> None:
26
+ return None
28
27
 
29
28
 
30
- def _serialize_case(case: SerializedCase) -> dict[str, Any]:
31
- return {
32
- "verbose_name": case.verbose_name,
33
- "path_template": case.path_template,
34
- "path_parameters": stringify_path_parameters(case.path_parameters),
35
- "query": prepare_query(case.query),
36
- "cookies": case.cookies,
37
- "media_type": case.media_type,
38
- }
29
+ def serialize_after_probing(event: events.AfterProbing) -> dict[str, Any] | None:
30
+ probes = event.probes or []
31
+ return {"probes": [probe.serialize() for probe in probes]}
32
+
33
+
34
+ def serialize_before_analysis(_: events.BeforeAnalysis) -> None:
35
+ return None
39
36
 
40
37
 
41
- def _serialize_response(response: Response) -> dict[str, Any]:
38
+ def serialize_after_analysis(event: events.AfterAnalysis) -> dict[str, Any] | None:
39
+ return event._serialize()
40
+
41
+
42
+ def serialize_before_execution(event: events.BeforeExecution) -> dict[str, Any] | None:
42
43
  return {
43
- "status_code": response.status_code,
44
- "headers": response.headers,
45
- "body": response.body,
46
- "encoding": response.encoding,
47
- "elapsed": response.elapsed,
44
+ "correlation_id": event.correlation_id,
45
+ "verbose_name": event.verbose_name,
46
+ "data_generation_method": event.data_generation_method,
48
47
  }
49
48
 
50
49
 
@@ -56,27 +55,7 @@ def serialize_after_execution(event: events.AfterExecution) -> dict[str, Any] |
56
55
  "elapsed_time": event.elapsed_time,
57
56
  "data_generation_method": event.data_generation_method,
58
57
  "result": {
59
- "checks": [
60
- {
61
- "name": check.name,
62
- "value": check.value,
63
- "request": {
64
- "method": check.request.method,
65
- "uri": check.request.uri,
66
- "body": check.request.body,
67
- "headers": check.request.headers,
68
- },
69
- "response": _serialize_response(check.response) if check.response is not None else None,
70
- "example": _serialize_case(check.example),
71
- "message": check.message,
72
- "context": asdict(check.context) if check.context is not None else None, # type: ignore
73
- "history": [
74
- {"case": _serialize_case(entry.case), "response": _serialize_response(entry.response)}
75
- for entry in check.history
76
- ],
77
- }
78
- for check in event.result.checks
79
- ],
58
+ "checks": [_serialize_check(check) for check in event.result.checks],
80
59
  "errors": [asdict(error) for error in event.result.errors],
81
60
  "skip_reason": event.result.skip_reason,
82
61
  },
@@ -114,12 +93,35 @@ def serialize_finished(event: events.Finished) -> dict[str, Any] | None:
114
93
  }
115
94
 
116
95
 
96
+ def serialize_stateful_event(event: events.StatefulEvent) -> dict[str, Any] | None:
97
+ return _serialize_stateful_event(event.data)
98
+
99
+
100
+ def _serialize_stateful_event(event: stateful_events.StatefulEvent) -> dict[str, Any] | None:
101
+ return {"data": {event.__class__.__name__: event.asdict()}}
102
+
103
+
104
+ def serialize_after_stateful_execution(event: events.AfterStatefulExecution) -> dict[str, Any] | None:
105
+ return {
106
+ "status": event.status,
107
+ "data_generation_method": event.data_generation_method,
108
+ "result": asdict(event.result),
109
+ "elapsed_time": event.elapsed_time,
110
+ }
111
+
112
+
117
113
  SERIALIZER_MAP = {
118
114
  events.Initialized: serialize_initialized,
115
+ events.BeforeProbing: serialize_before_probing,
116
+ events.AfterProbing: serialize_after_probing,
117
+ events.BeforeAnalysis: serialize_before_analysis,
118
+ events.AfterAnalysis: serialize_after_analysis,
119
119
  events.BeforeExecution: serialize_before_execution,
120
120
  events.AfterExecution: serialize_after_execution,
121
121
  events.Interrupted: serialize_interrupted,
122
122
  events.InternalError: serialize_internal_error,
123
+ events.StatefulEvent: serialize_stateful_event,
124
+ events.AfterStatefulExecution: serialize_after_stateful_execution,
123
125
  events.Finished: serialize_finished,
124
126
  }
125
127
 
@@ -128,10 +130,16 @@ def serialize_event(
128
130
  event: events.ExecutionEvent,
129
131
  *,
130
132
  on_initialized: SerializeFunc | None = None,
133
+ on_before_probing: SerializeFunc | None = None,
134
+ on_after_probing: SerializeFunc | None = None,
135
+ on_before_analysis: SerializeFunc | None = None,
136
+ on_after_analysis: SerializeFunc | None = None,
131
137
  on_before_execution: SerializeFunc | None = None,
132
138
  on_after_execution: SerializeFunc | None = None,
133
139
  on_interrupted: SerializeFunc | None = None,
134
140
  on_internal_error: SerializeFunc | None = None,
141
+ on_stateful_event: SerializeFunc | None = None,
142
+ on_after_stateful_execution: SerializeFunc | None = None,
135
143
  on_finished: SerializeFunc | None = None,
136
144
  extra: dict[str, Any] | None = None,
137
145
  ) -> dict[str, dict[str, Any] | None]:
@@ -139,10 +147,16 @@ def serialize_event(
139
147
  # Use the explicitly provided serializer for this event and fallback to default one if it is not provided
140
148
  serializer = {
141
149
  events.Initialized: on_initialized,
150
+ events.BeforeProbing: on_before_probing,
151
+ events.AfterProbing: on_after_probing,
152
+ events.BeforeAnalysis: on_before_analysis,
153
+ events.AfterAnalysis: on_after_analysis,
142
154
  events.BeforeExecution: on_before_execution,
143
155
  events.AfterExecution: on_after_execution,
144
156
  events.Interrupted: on_interrupted,
145
157
  events.InternalError: on_internal_error,
158
+ events.StatefulEvent: on_stateful_event,
159
+ events.AfterStatefulExecution: on_after_stateful_execution,
146
160
  events.Finished: on_finished,
147
161
  }.get(event.__class__)
148
162
  if serializer is None:
@@ -157,28 +171,3 @@ def serialize_event(
157
171
  data = merge_recursively(data, extra)
158
172
  # Externally tagged structure
159
173
  return {event.__class__.__name__: data}
160
-
161
-
162
- def stringify_path_parameters(path_parameters: dict[str, Any] | None) -> dict[str, str]:
163
- """Cast all path parameter values to strings.
164
-
165
- Path parameter values may be of arbitrary type, but to display them properly they should be casted to strings.
166
- """
167
- return {key: str(value) for key, value in (path_parameters or {}).items()}
168
-
169
-
170
- def prepare_query(query: dict[str, Any] | None) -> dict[str, list[str]]:
171
- """Convert all query values to list of strings.
172
-
173
- Query parameters may be generated in different shapes, including integers, strings, list of strings, etc.
174
- It can also be an object, if the schema contains an object, but `style` and `explode` combo is not applicable.
175
- """
176
-
177
- def to_list_of_strings(value: Any) -> list[str]:
178
- if isinstance(value, list):
179
- return list(map(str, value))
180
- if isinstance(value, str):
181
- return [value]
182
- return [str(value)]
183
-
184
- return {key: to_list_of_strings(value) for key, value in (query or {}).items()}
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  import sys
3
4
  from typing import Any
4
5
 
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from ...models import APIOperation
8
+ from ...schemas import APIOperationMap
9
+
10
+
11
+ @dataclass
12
+ class OperationCache:
13
+ _maps: dict[str, APIOperationMap] = field(default_factory=dict)
14
+ _operations: dict[str, APIOperation] = field(default_factory=dict)
15
+
16
+ def get_map(self, key: str) -> APIOperationMap | None:
17
+ return self._maps.get(key)
18
+
19
+ def insert_map(self, key: str, value: APIOperationMap) -> None:
20
+ self._maps[key] = value
21
+
22
+ def get_operation(self, key: str) -> APIOperation | None:
23
+ return self._operations.get(key)
24
+
25
+ def insert_operation(self, key: str, value: APIOperation) -> None:
26
+ self._operations[key] = value
@@ -7,7 +7,7 @@ from json import JSONDecodeError
7
7
  from typing import IO, TYPE_CHECKING, Any, Callable, Dict, NoReturn, cast
8
8
 
9
9
  from ...code_samples import CodeSampleStyle
10
- from ...constants import WAIT_FOR_SCHEMA_INTERVAL
10
+ from ...constants import DEFAULT_RESPONSE_TIMEOUT, WAIT_FOR_SCHEMA_INTERVAL
11
11
  from ...exceptions import SchemaError, SchemaErrorType
12
12
  from ...generation import (
13
13
  DEFAULT_DATA_GENERATION_METHODS,
@@ -16,11 +16,12 @@ 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
22
23
  from ...transports.headers import setup_default_headers
23
- from ...types import PathLike
24
+ from ...types import PathLike, Specification
24
25
 
25
26
  if TYPE_CHECKING:
26
27
  from graphql import DocumentNode
@@ -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,
@@ -135,11 +139,12 @@ def from_url(
135
139
  interval=WAIT_FOR_SCHEMA_INTERVAL,
136
140
  )
137
141
  def _load_schema(_uri: str, **_kwargs: Any) -> requests.Response:
138
- return requests.post(_uri, **kwargs)
142
+ return requests.post(_uri, **_kwargs)
139
143
 
140
144
  else:
141
145
  _load_schema = requests.post
142
146
 
147
+ kwargs.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT / 1000)
143
148
  response = load_schema_from_url(lambda: _load_schema(url, **kwargs))
144
149
  raw_schema = extract_schema_from_response(response)
145
150
  return from_dict(
@@ -161,6 +166,7 @@ def from_file(
161
166
  base_url: str | None = None,
162
167
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
163
168
  generation_config: GenerationConfig | None = None,
169
+ output_config: OutputConfig | None = None,
164
170
  code_sample_style: str = CodeSampleStyle.default().name,
165
171
  location: str | None = None,
166
172
  rate_limit: str | None = None,
@@ -198,6 +204,8 @@ def from_file(
198
204
  app=app,
199
205
  base_url=base_url,
200
206
  data_generation_methods=data_generation_methods,
207
+ generation_config=generation_config,
208
+ output_config=output_config,
201
209
  code_sample_style=code_sample_style,
202
210
  location=location,
203
211
  rate_limit=rate_limit,
@@ -221,6 +229,7 @@ def from_dict(
221
229
  location: str | None = None,
222
230
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
223
231
  generation_config: GenerationConfig | None = None,
232
+ output_config: OutputConfig | None = None,
224
233
  code_sample_style: str = CodeSampleStyle.default().name,
225
234
  rate_limit: str | None = None,
226
235
  sanitize_output: bool = True,
@@ -233,6 +242,7 @@ def from_dict(
233
242
  :param app: A WSGI app instance.
234
243
  :return: GraphQLSchema
235
244
  """
245
+ from ... import transports
236
246
  from .schemas import GraphQLSchema
237
247
 
238
248
  _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
@@ -245,13 +255,17 @@ def from_dict(
245
255
  rate_limiter = build_limiter(rate_limit)
246
256
  instance = GraphQLSchema(
247
257
  raw_schema,
258
+ specification=Specification.GRAPHQL,
248
259
  location=location,
249
260
  base_url=base_url,
250
261
  app=app,
251
262
  data_generation_methods=DataGenerationMethod.ensure_list(data_generation_methods),
263
+ generation_config=generation_config or GenerationConfig(),
264
+ output_config=output_config or OutputConfig(),
252
265
  code_sample_style=_code_sample_style,
253
266
  rate_limiter=rate_limiter,
254
267
  sanitize_output=sanitize_output,
268
+ transport=transports.get(app),
255
269
  ) # type: ignore
256
270
  dispatch("after_load_schema", hook_context, instance)
257
271
  return instance
@@ -264,6 +278,7 @@ def from_wsgi(
264
278
  base_url: str | None = None,
265
279
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
266
280
  generation_config: GenerationConfig | None = None,
281
+ output_config: OutputConfig | None = None,
267
282
  code_sample_style: str = CodeSampleStyle.default().name,
268
283
  rate_limit: str | None = None,
269
284
  sanitize_output: bool = True,
@@ -292,6 +307,8 @@ def from_wsgi(
292
307
  base_url=base_url,
293
308
  app=app,
294
309
  data_generation_methods=data_generation_methods,
310
+ generation_config=generation_config,
311
+ output_config=output_config,
295
312
  code_sample_style=code_sample_style,
296
313
  rate_limit=rate_limit,
297
314
  sanitize_output=sanitize_output,
@@ -305,6 +322,7 @@ def from_asgi(
305
322
  base_url: str | None = None,
306
323
  data_generation_methods: DataGenerationMethodInput = DEFAULT_DATA_GENERATION_METHODS,
307
324
  generation_config: GenerationConfig | None = None,
325
+ output_config: OutputConfig | None = None,
308
326
  code_sample_style: str = CodeSampleStyle.default().name,
309
327
  rate_limit: str | None = None,
310
328
  sanitize_output: bool = True,
@@ -330,6 +348,8 @@ def from_asgi(
330
348
  base_url=base_url,
331
349
  app=app,
332
350
  data_generation_methods=data_generation_methods,
351
+ generation_config=generation_config,
352
+ output_config=output_config,
333
353
  code_sample_style=code_sample_style,
334
354
  rate_limit=rate_limit,
335
355
  sanitize_output=sanitize_output,
@@ -337,8 +357,8 @@ def from_asgi(
337
357
 
338
358
 
339
359
  def get_loader_for_app(app: Any) -> Callable:
340
- from starlette.applications import Starlette
360
+ from ...transports.asgi import is_asgi_app
341
361
 
342
- if isinstance(app, Starlette):
362
+ if is_asgi_app(app):
343
363
  return from_asgi
344
364
  return from_wsgi
@@ -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