schemathesis 3.26.2__py3-none-any.whl → 3.27.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/models.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+
2
3
  import datetime
3
4
  import inspect
4
5
  import textwrap
@@ -6,7 +7,7 @@ from collections import Counter
6
7
  from contextlib import contextmanager
7
8
  from dataclasses import dataclass, field
8
9
  from enum import Enum
9
- from functools import partial, lru_cache
10
+ from functools import lru_cache, partial
10
11
  from itertools import chain
11
12
  from logging import LogRecord
12
13
  from typing import (
@@ -25,51 +26,48 @@ from typing import (
25
26
  )
26
27
  from urllib.parse import quote, unquote, urljoin, urlparse, urlsplit, urlunsplit
27
28
 
28
- from urllib3.exceptions import ReadTimeoutError
29
-
30
- from . import failures, serializers
29
+ from . import serializers
31
30
  from ._dependency_versions import IS_WERKZEUG_ABOVE_3
32
31
  from .auths import AuthStorage
33
32
  from .code_samples import CodeSampleStyle
34
- from .generation import DataGenerationMethod, GenerationConfig
35
33
  from .constants import (
36
- DEFAULT_RESPONSE_TIMEOUT,
34
+ NOT_SET,
37
35
  SCHEMATHESIS_TEST_CASE_HEADER,
38
36
  SERIALIZERS_SUGGESTION_MESSAGE,
39
37
  USER_AGENT,
40
- NOT_SET,
41
38
  )
42
39
  from .exceptions import (
43
- maybe_set_assertion_message,
44
40
  CheckFailed,
45
41
  FailureContext,
46
42
  OperationSchemaError,
47
43
  SerializationNotPossible,
44
+ SkipTest,
48
45
  deduplicate_failed_checks,
49
46
  get_grouped_exception,
50
- get_timeout_error,
47
+ maybe_set_assertion_message,
51
48
  prepare_response_payload,
52
- SkipTest,
53
49
  )
54
- from .internal.deprecation import deprecated_property
55
- from .internal.copy import fast_deepcopy
50
+ from .generation import DataGenerationMethod, GenerationConfig, generate_random_case_id
56
51
  from .hooks import GLOBAL_HOOK_DISPATCHER, HookContext, HookDispatcher, dispatch
52
+ from .internal.copy import fast_deepcopy
53
+ from .internal.deprecation import deprecated_property, deprecated_function
57
54
  from .parameters import Parameter, ParameterSet, PayloadAlternatives
58
55
  from .sanitization import sanitize_request, sanitize_response
59
- from .serializers import Serializer, SerializerContext
60
- from .transports import serialize_payload
56
+ from .serializers import Serializer
57
+ from .transports import ASGITransport, RequestsTransport, WSGITransport, serialize_payload
61
58
  from .types import Body, Cookies, FormData, Headers, NotSet, PathParameters, Query
62
- from .generation import generate_random_case_id
63
59
 
64
60
  if TYPE_CHECKING:
65
- import werkzeug
66
61
  import unittest
67
- from requests.structures import CaseInsensitiveDict
68
- from hypothesis import strategies as st
62
+
69
63
  import requests.auth
70
- from .transports.responses import GenericResponse, WSGIResponse
64
+ import werkzeug
65
+ from hypothesis import strategies as st
66
+ from requests.structures import CaseInsensitiveDict
67
+
71
68
  from .schemas import BaseSchema
72
69
  from .stateful import Stateful, StatefulTest
70
+ from .transports.responses import GenericResponse, WSGIResponse
73
71
 
74
72
 
75
73
  @dataclass
@@ -86,7 +84,7 @@ class CaseSource:
86
84
 
87
85
  def cant_serialize(media_type: str) -> NoReturn: # type: ignore
88
86
  """Reject the current example if we don't know how to send this data to the application."""
89
- from hypothesis import note, event, reject
87
+ from hypothesis import event, note, reject
90
88
 
91
89
  event_text = f"Can't serialize data to `{media_type}`."
92
90
  note(f"{event_text} {SERIALIZERS_SUGGESTION_MESSAGE}")
@@ -210,7 +208,7 @@ class Case:
210
208
 
211
209
  def prepare_code_sample_data(self, headers: dict[str, Any] | None) -> PreparedRequestData:
212
210
  base_url = self.get_full_base_url()
213
- kwargs = self.as_requests_kwargs(base_url, headers=headers)
211
+ kwargs = RequestsTransport().serialize_case(self, base_url=base_url, headers=headers)
214
212
  return prepare_request_data(kwargs)
215
213
 
216
214
  def get_code_to_reproduce(
@@ -286,40 +284,17 @@ class Case:
286
284
  return cls()
287
285
  return None
288
286
 
287
+ def _get_body(self) -> Body | NotSet:
288
+ return self.body
289
+
290
+ @deprecated_function(removed_in="4.0", replacement="Case.as_transport_kwargs")
289
291
  def as_requests_kwargs(self, base_url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
290
292
  """Convert the case into a dictionary acceptable by requests."""
291
- final_headers = self._get_headers(headers)
292
- if self.media_type and self.media_type != "multipart/form-data" and not isinstance(self.body, NotSet):
293
- # `requests` will handle multipart form headers with the proper `boundary` value.
294
- if "content-type" not in {header.lower() for header in final_headers}:
295
- final_headers["Content-Type"] = self.media_type
296
- base_url = self._get_base_url(base_url)
297
- formatted_path = self.formatted_path.lstrip("/")
298
- if not base_url.endswith("/"):
299
- base_url += "/"
300
- url = unquote(urljoin(base_url, quote(formatted_path)))
301
- extra: dict[str, Any]
302
- serializer = self._get_serializer()
303
- if serializer is not None and not isinstance(self.body, NotSet):
304
- context = SerializerContext(case=self)
305
- extra = serializer.as_requests(context, self.body)
306
- else:
307
- extra = {}
308
- if self._auth is not None:
309
- extra["auth"] = self._auth
310
- additional_headers = extra.pop("headers", None)
311
- if additional_headers:
312
- # Additional headers, needed for the serializer
313
- for key, value in additional_headers.items():
314
- final_headers.setdefault(key, value)
315
- return {
316
- "method": self.method,
317
- "url": url,
318
- "cookies": self.cookies,
319
- "headers": final_headers,
320
- "params": self.query,
321
- **extra,
322
- }
293
+ return RequestsTransport().serialize_case(self, base_url=base_url, headers=headers)
294
+
295
+ def as_transport_kwargs(self, base_url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
296
+ """Convert the test case into a dictionary acceptable by the underlying transport call."""
297
+ return self.operation.schema.transport.serialize_case(self, base_url=base_url, headers=headers)
323
298
 
324
299
  def call(
325
300
  self,
@@ -329,83 +304,21 @@ class Case:
329
304
  params: dict[str, Any] | None = None,
330
305
  cookies: dict[str, Any] | None = None,
331
306
  **kwargs: Any,
332
- ) -> requests.Response:
333
- import requests
334
-
335
- """Make a network call with `requests`."""
307
+ ) -> GenericResponse:
336
308
  hook_context = HookContext(operation=self.operation)
337
309
  dispatch("before_call", hook_context, self)
338
- data = self.as_requests_kwargs(base_url, headers)
339
- data.update(kwargs)
340
- if params is not None:
341
- _merge_dict_to(data, "params", params)
342
- if cookies is not None:
343
- _merge_dict_to(data, "cookies", cookies)
344
- data.setdefault("timeout", DEFAULT_RESPONSE_TIMEOUT / 1000)
345
- if session is None:
346
- validate_vanilla_requests_kwargs(data)
347
- session = requests.Session()
348
- close_session = True
349
- else:
350
- close_session = False
351
- verify = data.get("verify", True)
352
- try:
353
- with self.operation.schema.ratelimit():
354
- response = session.request(**data) # type: ignore
355
- except (requests.Timeout, requests.ConnectionError) as exc:
356
- if isinstance(exc, requests.ConnectionError):
357
- if not isinstance(exc.args[0], ReadTimeoutError):
358
- raise
359
- req = requests.Request(
360
- method=data["method"].upper(),
361
- url=data["url"],
362
- headers=data["headers"],
363
- files=data.get("files"),
364
- data=data.get("data") or {},
365
- json=data.get("json"),
366
- params=data.get("params") or {},
367
- auth=data.get("auth"),
368
- cookies=data["cookies"],
369
- hooks=data.get("hooks"),
370
- )
371
- request = session.prepare_request(req)
372
- else:
373
- request = cast(requests.PreparedRequest, exc.request)
374
- timeout = 1000 * data["timeout"] # It is defined and not empty, since the exception happened
375
- code_message = self._get_code_message(self.operation.schema.code_sample_style, request, verify=verify)
376
- message = f"The server failed to respond within the specified limit of {timeout:.2f}ms"
377
- raise get_timeout_error(timeout)(
378
- f"\n\n1. {failures.RequestTimeout.title}\n\n{message}\n\n{code_message}",
379
- context=failures.RequestTimeout(message=message, timeout=timeout),
380
- ) from None
381
- response.verify = verify # type: ignore[attr-defined]
310
+ response = self.operation.schema.transport.send(
311
+ self, session=session, base_url=base_url, headers=headers, params=params, cookies=cookies, **kwargs
312
+ )
382
313
  dispatch("after_call", hook_context, self, response)
383
- if close_session:
384
- session.close()
385
314
  return response
386
315
 
316
+ @deprecated_function(removed_in="4.0", replacement="Case.as_transport_kwargs")
387
317
  def as_werkzeug_kwargs(self, headers: dict[str, str] | None = None) -> dict[str, Any]:
388
318
  """Convert the case into a dictionary acceptable by werkzeug.Client."""
389
- final_headers = self._get_headers(headers)
390
- if self.media_type and not isinstance(self.body, NotSet):
391
- # If we need to send a payload, then the Content-Type header should be set
392
- final_headers["Content-Type"] = self.media_type
393
- extra: dict[str, Any]
394
- serializer = self._get_serializer()
395
- if serializer is not None and not isinstance(self.body, NotSet):
396
- context = SerializerContext(case=self)
397
- extra = serializer.as_werkzeug(context, self.body)
398
- else:
399
- extra = {}
400
- return {
401
- "method": self.method,
402
- "path": self.operation.schema.get_full_path(self.formatted_path),
403
- # Convert to a regular dictionary, as we use `CaseInsensitiveDict` which is not supported by Werkzeug
404
- "headers": dict(final_headers),
405
- "query_string": self.query,
406
- **extra,
407
- }
319
+ return WSGITransport(self.app).serialize_case(self, headers=headers)
408
320
 
321
+ @deprecated_function(removed_in="4.0", replacement="Case.call")
409
322
  def call_wsgi(
410
323
  self,
411
324
  app: Any = None,
@@ -413,10 +326,6 @@ class Case:
413
326
  query_string: dict[str, str] | None = None,
414
327
  **kwargs: Any,
415
328
  ) -> WSGIResponse:
416
- from .transports.responses import WSGIResponse
417
- import werkzeug
418
- import requests
419
-
420
329
  application = app or self.app
421
330
  if application is None:
422
331
  raise RuntimeError(
@@ -425,17 +334,11 @@ class Case:
425
334
  )
426
335
  hook_context = HookContext(operation=self.operation)
427
336
  dispatch("before_call", hook_context, self)
428
- data = self.as_werkzeug_kwargs(headers)
429
- if query_string is not None:
430
- _merge_dict_to(data, "query_string", query_string)
431
- client = werkzeug.Client(application, WSGIResponse)
432
- with cookie_handler(client, self.cookies), self.operation.schema.ratelimit():
433
- response = client.open(**data, **kwargs)
434
- requests_kwargs = self.as_requests_kwargs(base_url=self.get_full_base_url(), headers=headers)
435
- response.request = requests.Request(**requests_kwargs).prepare()
337
+ response = WSGITransport(application).send(self, headers=headers, params=query_string, **kwargs)
436
338
  dispatch("after_call", hook_context, self, response)
437
339
  return response
438
340
 
341
+ @deprecated_function(removed_in="4.0", replacement="Case.call")
439
342
  def call_asgi(
440
343
  self,
441
344
  app: Any = None,
@@ -443,19 +346,17 @@ class Case:
443
346
  headers: dict[str, str] | None = None,
444
347
  **kwargs: Any,
445
348
  ) -> requests.Response:
446
- from starlette_testclient import TestClient as ASGIClient
447
-
448
349
  application = app or self.app
449
350
  if application is None:
450
351
  raise RuntimeError(
451
352
  "ASGI application instance is required. "
452
353
  "Please, set `app` argument in the schema constructor or pass it to `call_asgi`"
453
354
  )
454
- if base_url is None:
455
- base_url = self.get_full_base_url()
456
-
457
- with ASGIClient(application) as client:
458
- return self.call(base_url=base_url, session=client, headers=headers, **kwargs)
355
+ hook_context = HookContext(operation=self.operation)
356
+ dispatch("before_call", hook_context, self)
357
+ response = ASGITransport(application).send(self, base_url=base_url, headers=headers, **kwargs)
358
+ dispatch("after_call", hook_context, self, response)
359
+ return response
459
360
 
460
361
  def validate_response(
461
362
  self,
@@ -560,12 +461,19 @@ class Case:
560
461
  self.validate_response(response, checks, code_sample_style=code_sample_style)
561
462
  return response
562
463
 
464
+ def _get_url(self, base_url: str | None) -> str:
465
+ base_url = self._get_base_url(base_url)
466
+ formatted_path = self.formatted_path.lstrip("/")
467
+ if not base_url.endswith("/"):
468
+ base_url += "/"
469
+ return unquote(urljoin(base_url, quote(formatted_path)))
470
+
563
471
  def get_full_url(self) -> str:
564
472
  """Make a full URL to the current API operation, including query parameters."""
565
473
  import requests
566
474
 
567
475
  base_url = self.base_url or "http://127.0.0.1"
568
- kwargs = self.as_requests_kwargs(base_url)
476
+ kwargs = RequestsTransport().serialize_case(self, base_url=base_url)
569
477
  request = requests.Request(**kwargs)
570
478
  prepared = requests.Session().prepare_request(request) # type: ignore
571
479
  return cast(str, prepared.url)
@@ -922,7 +830,7 @@ class Request:
922
830
  import requests
923
831
 
924
832
  base_url = case.get_full_base_url()
925
- kwargs = case.as_requests_kwargs(base_url)
833
+ kwargs = RequestsTransport().serialize_case(case, base_url=base_url)
926
834
  request = requests.Request(**kwargs)
927
835
  prepared = session.prepare_request(request) # type: ignore
928
836
  return cls.from_prepared_request(prepared)
@@ -21,6 +21,8 @@ from jsonschema.exceptions import SchemaError as JsonSchemaError
21
21
  from jsonschema.exceptions import ValidationError
22
22
  from requests.auth import HTTPDigestAuth, _basic_auth_str
23
23
 
24
+ from schemathesis.transports import RequestsTransport
25
+
24
26
  from ... import failures, hooks
25
27
  from ..._compat import MultipleFailures
26
28
  from ..._hypothesis import (
@@ -297,10 +299,7 @@ def run_probes(schema: BaseSchema, config: probes.ProbeConfig) -> list[probes.Pr
297
299
  if isinstance(result.probe, probes.NullByteInHeader) and result.is_failure:
298
300
  from ...specs.openapi._hypothesis import HEADER_FORMAT, header_values
299
301
 
300
- formats.register(
301
- HEADER_FORMAT,
302
- header_values(blacklist_characters="\n\r\x00").map(str.lstrip),
303
- )
302
+ formats.register(HEADER_FORMAT, header_values(blacklist_characters="\n\r\x00"))
304
303
  return results
305
304
 
306
305
 
@@ -849,7 +848,7 @@ def _network_test(
849
848
  response = case.call(**kwargs)
850
849
  except CheckFailed as exc:
851
850
  check_name = "request_timeout"
852
- requests_kwargs = case.as_requests_kwargs(base_url=case.get_full_base_url(), headers=headers)
851
+ requests_kwargs = RequestsTransport().serialize_case(case, base_url=case.get_full_base_url(), headers=headers)
853
852
  request = requests.Request(**requests_kwargs).prepare()
854
853
  elapsed = cast(float, timeout) # It is defined and not empty, since the exception happened
855
854
  check_result = result.add_failure(
@@ -939,14 +938,14 @@ def _wsgi_test(
939
938
  feedback: Feedback,
940
939
  max_response_time: int | None,
941
940
  ) -> WSGIResponse:
941
+ from ...transports.responses import WSGIResponse
942
+
942
943
  with catching_logs(LogCaptureHandler(), level=logging.DEBUG) as recorded:
943
- start = time.monotonic()
944
944
  hook_context = HookContext(operation=case.operation)
945
- kwargs = {"headers": headers}
945
+ kwargs: dict[str, Any] = {"headers": headers}
946
946
  hooks.dispatch("process_call_kwargs", hook_context, case, kwargs)
947
- response = case.call_wsgi(**kwargs)
948
- elapsed = time.monotonic() - start
949
- context = TargetContext(case=case, response=response, response_time=elapsed)
947
+ response = cast(WSGIResponse, case.call(**kwargs))
948
+ context = TargetContext(case=case, response=response, response_time=response.elapsed.total_seconds())
950
949
  run_targets(targets, context)
951
950
  result.logs.extend(recorded.records)
952
951
  status = Status.success
@@ -967,7 +966,7 @@ def _wsgi_test(
967
966
  finally:
968
967
  feedback.add_test_case(case, response)
969
968
  if store_interactions:
970
- result.store_wsgi_response(case, response, headers, elapsed, status, check_results)
969
+ result.store_wsgi_response(case, response, headers, response.elapsed.total_seconds(), status, check_results)
971
970
  return response
972
971
 
973
972
 
@@ -1037,7 +1036,7 @@ def _asgi_test(
1037
1036
  hook_context = HookContext(operation=case.operation)
1038
1037
  kwargs: dict[str, Any] = {"headers": headers}
1039
1038
  hooks.dispatch("process_call_kwargs", hook_context, case, kwargs)
1040
- response = case.call_asgi(**kwargs)
1039
+ response = case.call(**kwargs)
1041
1040
  context = TargetContext(case=case, response=response, response_time=response.elapsed.total_seconds())
1042
1041
  run_targets(targets, context)
1043
1042
  status = Status.success
@@ -4,28 +4,30 @@ They all consist of primitive types and don't have references to schemas, app, e
4
4
  """
5
5
 
6
6
  from __future__ import annotations
7
+
7
8
  import logging
8
9
  import re
10
+ import textwrap
9
11
  from dataclasses import dataclass, field
10
- from typing import Any, TYPE_CHECKING, cast
12
+ from typing import TYPE_CHECKING, Any, cast
11
13
 
12
- from ..transports import serialize_payload
13
14
  from ..code_samples import get_excluded_headers
14
15
  from ..exceptions import (
16
+ BodyInGetRequestError,
17
+ DeadlineExceeded,
15
18
  FailureContext,
16
19
  InternalError,
17
- make_unique_by_key,
18
- format_exception,
19
- extract_requests_exception_details,
20
- RuntimeErrorType,
21
- DeadlineExceeded,
22
- OperationSchemaError,
23
- BodyInGetRequestError,
24
20
  InvalidRegularExpression,
21
+ OperationSchemaError,
22
+ RuntimeErrorType,
25
23
  SerializationError,
26
24
  UnboundPrefixError,
25
+ extract_requests_exception_details,
26
+ format_exception,
27
+ make_unique_by_key,
27
28
  )
28
29
  from ..models import Case, Check, Interaction, Request, Response, Status, TestResult
30
+ from ..transports import serialize_payload
29
31
 
30
32
  if TYPE_CHECKING:
31
33
  import hypothesis.errors
@@ -108,6 +110,7 @@ class SerializedCheck:
108
110
  @classmethod
109
111
  def from_check(cls, check: Check) -> SerializedCheck:
110
112
  import requests
113
+
111
114
  from ..transports.responses import WSGIResponse
112
115
 
113
116
  if check.response is not None:
@@ -140,6 +143,25 @@ class SerializedCheck:
140
143
  history=history,
141
144
  )
142
145
 
146
+ @property
147
+ def title(self) -> str:
148
+ if self.context is not None:
149
+ return self.context.title
150
+ return f"Custom check failed: `{self.name}`"
151
+
152
+ @property
153
+ def formatted_message(self) -> str | None:
154
+ if self.context is not None:
155
+ if self.context.message:
156
+ message = self.context.message
157
+ else:
158
+ message = None
159
+ else:
160
+ message = self.message
161
+ if message is not None:
162
+ message = textwrap.indent(message, prefix=" ")
163
+ return message
164
+
143
165
 
144
166
  def _get_headers(headers: dict[str, Any] | CaseInsensitiveDict) -> dict[str, str]:
145
167
  return {key: value[0] for key, value in headers.items() if key not in get_excluded_headers()}
@@ -203,8 +225,8 @@ class SerializedError:
203
225
 
204
226
  @classmethod
205
227
  def from_exception(cls, exception: Exception) -> SerializedError:
206
- import requests
207
228
  import hypothesis.errors
229
+ import requests
208
230
  from hypothesis import HealthCheck
209
231
 
210
232
  title = "Runtime Error"
schemathesis/schemas.py CHANGED
@@ -8,11 +8,13 @@ They give only static definitions of paths.
8
8
  """
9
9
 
10
10
  from __future__ import annotations
11
+
11
12
  from collections.abc import Mapping, MutableMapping
12
13
  from contextlib import nullcontext
13
14
  from dataclasses import dataclass, field
14
15
  from functools import lru_cache
15
16
  from typing import (
17
+ TYPE_CHECKING,
16
18
  Any,
17
19
  Callable,
18
20
  ContextManager,
@@ -22,7 +24,6 @@ from typing import (
22
24
  NoReturn,
23
25
  Sequence,
24
26
  TypeVar,
25
- TYPE_CHECKING,
26
27
  )
27
28
  from urllib.parse import quote, unquote, urljoin, urlparse, urlsplit, urlunsplit
28
29
 
@@ -30,23 +31,23 @@ import hypothesis
30
31
  from hypothesis.strategies import SearchStrategy
31
32
  from pyrate_limiter import Limiter
32
33
 
33
- from .constants import NOT_SET
34
+ from ._dependency_versions import IS_PYRATE_LIMITER_ABOVE_3
34
35
  from ._hypothesis import create_test
35
36
  from .auths import AuthStorage
36
37
  from .code_samples import CodeSampleStyle
38
+ from .constants import NOT_SET
39
+ from .exceptions import OperationSchemaError, UsageError
37
40
  from .generation import (
38
41
  DEFAULT_DATA_GENERATION_METHODS,
39
42
  DataGenerationMethod,
40
43
  DataGenerationMethodInput,
41
44
  GenerationConfig,
42
45
  )
43
- from ._dependency_versions import IS_PYRATE_LIMITER_ABOVE_3
44
- from .exceptions import OperationSchemaError, UsageError
45
46
  from .hooks import HookContext, HookDispatcher, HookScope, dispatch
46
- from .internal.result import Result, Ok
47
+ from .internal.result import Ok, Result
47
48
  from .models import APIOperation, Case
48
- from .stateful.state_machine import APIStateMachine
49
49
  from .stateful import Stateful, StatefulTest
50
+ from .stateful.state_machine import APIStateMachine
50
51
  from .types import (
51
52
  Body,
52
53
  Cookies,
@@ -58,9 +59,10 @@ from .types import (
58
59
  PathParameters,
59
60
  Query,
60
61
  )
61
- from .utils import PARAMETRIZE_MARKER, GivenInput, given_proxy, combine_strategies
62
+ from .utils import PARAMETRIZE_MARKER, GivenInput, combine_strategies, given_proxy
62
63
 
63
64
  if TYPE_CHECKING:
65
+ from .transports import Transport
64
66
  from .transports.responses import GenericResponse
65
67
 
66
68
 
@@ -75,6 +77,7 @@ def get_full_path(base_path: str, path: str) -> str:
75
77
  @dataclass(eq=False)
76
78
  class BaseSchema(Mapping):
77
79
  raw_schema: dict[str, Any]
80
+ transport: Transport
78
81
  location: str | None = None
79
82
  base_url: str | None = None
80
83
  method: Filter | None = None
@@ -346,6 +349,7 @@ class BaseSchema(Mapping):
346
349
  code_sample_style=code_sample_style, # type: ignore
347
350
  rate_limiter=rate_limiter, # type: ignore
348
351
  sanitize_output=sanitize_output, # type: ignore
352
+ transport=self.transport,
349
353
  )
350
354
 
351
355
  def get_local_hook_dispatcher(self) -> HookDispatcher | None:
@@ -234,6 +234,7 @@ def from_dict(
234
234
  :return: GraphQLSchema
235
235
  """
236
236
  from .schemas import GraphQLSchema
237
+ from ... import transports
237
238
 
238
239
  _code_sample_style = CodeSampleStyle.from_str(code_sample_style)
239
240
  hook_context = HookContext()
@@ -252,6 +253,7 @@ def from_dict(
252
253
  code_sample_style=_code_sample_style,
253
254
  rate_limiter=rate_limiter,
254
255
  sanitize_output=sanitize_output,
256
+ transport=transports.get(app),
255
257
  ) # type: ignore
256
258
  dispatch("after_load_schema", hook_context, instance)
257
259
  return instance
@@ -20,7 +20,6 @@ from typing import (
20
20
  from urllib.parse import urlsplit, urlunsplit
21
21
 
22
22
  import graphql
23
- import requests
24
23
  from graphql import GraphQLNamedType
25
24
  from hypothesis import strategies as st
26
25
  from hypothesis.strategies import SearchStrategy
@@ -60,39 +59,15 @@ class RootType(enum.Enum):
60
59
 
61
60
  @dataclass(repr=False)
62
61
  class GraphQLCase(Case):
63
- def as_requests_kwargs(self, base_url: str | None = None, headers: dict[str, str] | None = None) -> dict[str, Any]:
64
- final_headers = self._get_headers(headers)
62
+ def _get_url(self, base_url: str | None) -> str:
65
63
  base_url = self._get_base_url(base_url)
66
64
  # Replace the path, in case if the user provided any path parameters via hooks
67
65
  parts = list(urlsplit(base_url))
68
66
  parts[2] = self.formatted_path
69
- kwargs: dict[str, Any] = {
70
- "method": self.method,
71
- "url": urlunsplit(parts),
72
- "headers": final_headers,
73
- "cookies": self.cookies,
74
- "params": self.query,
75
- }
76
- # There is no direct way to have bytes here, but it is a useful pattern to support.
77
- # It also unifies GraphQLCase with its Open API counterpart where bytes may come from external examples
78
- if isinstance(self.body, bytes):
79
- kwargs["data"] = self.body
80
- # Assume that the payload is JSON, not raw GraphQL queries
81
- kwargs["headers"].setdefault("Content-Type", "application/json")
82
- else:
83
- kwargs["json"] = {"query": self.body}
84
- return kwargs
85
-
86
- def as_werkzeug_kwargs(self, headers: dict[str, str] | None = None) -> dict[str, Any]:
87
- final_headers = self._get_headers(headers)
88
- return {
89
- "method": self.method,
90
- "path": self.operation.schema.get_full_path(self.formatted_path),
91
- # Convert to a regular dictionary, as we use `CaseInsensitiveDict` which is not supported by Werkzeug
92
- "headers": dict(final_headers),
93
- "query_string": self.query,
94
- "json": {"query": self.body},
95
- }
67
+ return urlunsplit(parts)
68
+
69
+ def _get_body(self) -> Body | NotSet:
70
+ return self.body if isinstance(self.body, (NotSet, bytes)) else {"query": self.body}
96
71
 
97
72
  def validate_response(
98
73
  self,
@@ -107,15 +82,6 @@ class GraphQLCase(Case):
107
82
  checks = tuple(check for check in checks if check not in excluded_checks)
108
83
  return super().validate_response(response, checks, code_sample_style=code_sample_style)
109
84
 
110
- def call_asgi(
111
- self,
112
- app: Any = None,
113
- base_url: str | None = None,
114
- headers: dict[str, str] | None = None,
115
- **kwargs: Any,
116
- ) -> requests.Response:
117
- return super().call_asgi(app=app, base_url=base_url, headers=headers, **kwargs)
118
-
119
85
 
120
86
  C = TypeVar("C", bound=Case)
121
87
 
@@ -287,7 +253,7 @@ class GraphQLSchema(BaseSchema):
287
253
  cookies=cookies,
288
254
  query=query,
289
255
  body=body,
290
- media_type=media_type,
256
+ media_type=media_type or "application/json",
291
257
  generation_time=0.0,
292
258
  )
293
259
 
@@ -373,6 +339,7 @@ def get_case_strategy(
373
339
  operation=operation,
374
340
  data_generation_method=data_generation_method,
375
341
  generation_time=time.monotonic() - start,
342
+ media_type="application/json",
376
343
  ) # type: ignore
377
344
  context = auths.AuthContext(
378
345
  operation=operation,