schemathesis 3.15.4__py3-none-any.whl → 4.4.2__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/__init__.py +53 -25
- schemathesis/auths.py +507 -0
- schemathesis/checks.py +190 -25
- schemathesis/cli/__init__.py +27 -1219
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/commands/__init__.py +133 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +602 -0
- schemathesis/cli/commands/run/context.py +228 -0
- schemathesis/cli/commands/run/events.py +60 -0
- schemathesis/cli/commands/run/executor.py +157 -0
- schemathesis/cli/commands/run/filters.py +53 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +45 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +464 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +60 -0
- schemathesis/cli/commands/run/handlers/output.py +1750 -0
- schemathesis/cli/commands/run/loaders.py +118 -0
- schemathesis/cli/commands/run/validation.py +256 -0
- schemathesis/cli/constants.py +5 -0
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +203 -0
- schemathesis/cli/ext/options.py +81 -0
- schemathesis/config/__init__.py +202 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +101 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +163 -0
- schemathesis/config/_generation.py +157 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +335 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +253 -0
- schemathesis/config/_projects.py +543 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +89 -0
- schemathesis/config/schema.json +975 -0
- schemathesis/core/__init__.py +72 -0
- schemathesis/core/adapter.py +34 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +100 -0
- schemathesis/core/deserialization.py +210 -0
- schemathesis/core/errors.py +588 -0
- schemathesis/core/failures.py +316 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/jsonschema/__init__.py +13 -0
- schemathesis/core/jsonschema/bundler.py +183 -0
- schemathesis/core/jsonschema/keywords.py +40 -0
- schemathesis/core/jsonschema/references.py +222 -0
- schemathesis/core/jsonschema/types.py +41 -0
- schemathesis/core/lazy_import.py +15 -0
- schemathesis/core/loaders.py +107 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/core/media_types.py +79 -0
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/core/parameters.py +45 -0
- schemathesis/core/rate_limit.py +60 -0
- schemathesis/core/registries.py +34 -0
- schemathesis/core/result.py +27 -0
- schemathesis/core/schema_analysis.py +17 -0
- schemathesis/core/shell.py +203 -0
- schemathesis/core/transforms.py +144 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +73 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +152 -0
- schemathesis/engine/control.py +44 -0
- schemathesis/engine/core.py +201 -0
- schemathesis/engine/errors.py +446 -0
- schemathesis/engine/events.py +284 -0
- schemathesis/engine/observations.py +42 -0
- schemathesis/engine/phases/__init__.py +108 -0
- schemathesis/engine/phases/analysis.py +28 -0
- schemathesis/engine/phases/probes.py +172 -0
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +364 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +220 -0
- schemathesis/engine/phases/unit/_executor.py +459 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +254 -0
- schemathesis/errors.py +47 -0
- schemathesis/filters.py +395 -0
- schemathesis/generation/__init__.py +25 -0
- schemathesis/generation/case.py +478 -0
- schemathesis/generation/coverage.py +1528 -0
- schemathesis/generation/hypothesis/__init__.py +121 -0
- schemathesis/generation/hypothesis/builder.py +992 -0
- schemathesis/generation/hypothesis/examples.py +56 -0
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +285 -0
- schemathesis/generation/meta.py +227 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +127 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +294 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +285 -0
- schemathesis/hooks.py +270 -91
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +467 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +315 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +341 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/pytest/plugin.py +357 -0
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +682 -257
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/nodes.py +26 -2
- schemathesis/specs/graphql/scalars.py +77 -12
- schemathesis/specs/graphql/schemas.py +367 -148
- schemathesis/specs/graphql/validation.py +33 -0
- schemathesis/specs/openapi/__init__.py +9 -1
- schemathesis/specs/openapi/_hypothesis.py +555 -318
- schemathesis/specs/openapi/adapter/__init__.py +10 -0
- schemathesis/specs/openapi/adapter/parameters.py +729 -0
- schemathesis/specs/openapi/adapter/protocol.py +59 -0
- schemathesis/specs/openapi/adapter/references.py +19 -0
- schemathesis/specs/openapi/adapter/responses.py +368 -0
- schemathesis/specs/openapi/adapter/security.py +144 -0
- schemathesis/specs/openapi/adapter/v2.py +30 -0
- schemathesis/specs/openapi/adapter/v3_0.py +30 -0
- schemathesis/specs/openapi/adapter/v3_1.py +30 -0
- schemathesis/specs/openapi/analysis.py +96 -0
- schemathesis/specs/openapi/checks.py +748 -82
- schemathesis/specs/openapi/converter.py +176 -37
- schemathesis/specs/openapi/definitions.py +599 -4
- schemathesis/specs/openapi/examples.py +581 -165
- schemathesis/specs/openapi/expressions/__init__.py +52 -5
- schemathesis/specs/openapi/expressions/extractors.py +25 -0
- schemathesis/specs/openapi/expressions/lexer.py +34 -31
- schemathesis/specs/openapi/expressions/nodes.py +97 -46
- schemathesis/specs/openapi/expressions/parser.py +35 -13
- schemathesis/specs/openapi/formats.py +122 -0
- schemathesis/specs/openapi/media_types.py +75 -0
- schemathesis/specs/openapi/negative/__init__.py +93 -73
- schemathesis/specs/openapi/negative/mutations.py +294 -103
- schemathesis/specs/openapi/negative/utils.py +0 -9
- schemathesis/specs/openapi/patterns.py +458 -0
- schemathesis/specs/openapi/references.py +60 -81
- schemathesis/specs/openapi/schemas.py +647 -666
- schemathesis/specs/openapi/serialization.py +53 -30
- schemathesis/specs/openapi/stateful/__init__.py +403 -68
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/dependencies/__init__.py +232 -0
- schemathesis/specs/openapi/stateful/dependencies/inputs.py +428 -0
- schemathesis/specs/openapi/stateful/dependencies/models.py +341 -0
- schemathesis/specs/openapi/stateful/dependencies/naming.py +491 -0
- schemathesis/specs/openapi/stateful/dependencies/outputs.py +34 -0
- schemathesis/specs/openapi/stateful/dependencies/resources.py +339 -0
- schemathesis/specs/openapi/stateful/dependencies/schemas.py +447 -0
- schemathesis/specs/openapi/stateful/inference.py +254 -0
- schemathesis/specs/openapi/stateful/links.py +219 -78
- schemathesis/specs/openapi/types/__init__.py +3 -0
- schemathesis/specs/openapi/types/common.py +23 -0
- schemathesis/specs/openapi/types/v2.py +129 -0
- schemathesis/specs/openapi/types/v3.py +134 -0
- schemathesis/specs/openapi/utils.py +7 -6
- schemathesis/specs/openapi/warnings.py +75 -0
- schemathesis/transport/__init__.py +224 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +126 -0
- schemathesis/transport/requests.py +278 -0
- schemathesis/transport/serialization.py +329 -0
- schemathesis/transport/wsgi.py +175 -0
- schemathesis-4.4.2.dist-info/METADATA +213 -0
- schemathesis-4.4.2.dist-info/RECORD +192 -0
- {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info}/WHEEL +1 -1
- schemathesis-4.4.2.dist-info/entry_points.txt +6 -0
- {schemathesis-3.15.4.dist-info → schemathesis-4.4.2.dist-info/licenses}/LICENSE +1 -1
- schemathesis/_compat.py +0 -57
- schemathesis/_hypothesis.py +0 -123
- schemathesis/auth.py +0 -214
- schemathesis/cli/callbacks.py +0 -240
- schemathesis/cli/cassettes.py +0 -351
- schemathesis/cli/context.py +0 -38
- schemathesis/cli/debug.py +0 -21
- schemathesis/cli/handlers.py +0 -11
- schemathesis/cli/junitxml.py +0 -41
- schemathesis/cli/options.py +0 -70
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -521
- schemathesis/cli/output/short.py +0 -40
- schemathesis/constants.py +0 -88
- schemathesis/exceptions.py +0 -257
- schemathesis/extra/_aiohttp.py +0 -27
- schemathesis/extra/_flask.py +0 -10
- schemathesis/extra/_server.py +0 -16
- schemathesis/extra/pytest_plugin.py +0 -251
- schemathesis/failures.py +0 -145
- schemathesis/fixups/__init__.py +0 -29
- schemathesis/fixups/fast_api.py +0 -30
- schemathesis/graphql.py +0 -5
- schemathesis/internal.py +0 -6
- schemathesis/lazy.py +0 -301
- schemathesis/models.py +0 -1113
- schemathesis/parameters.py +0 -91
- schemathesis/runner/__init__.py +0 -470
- schemathesis/runner/events.py +0 -242
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/core.py +0 -791
- schemathesis/runner/impl/solo.py +0 -85
- schemathesis/runner/impl/threadpool.py +0 -367
- schemathesis/runner/serialization.py +0 -206
- schemathesis/serializers.py +0 -253
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -10
- schemathesis/service/client.py +0 -62
- schemathesis/service/constants.py +0 -25
- schemathesis/service/events.py +0 -39
- schemathesis/service/handler.py +0 -46
- schemathesis/service/hosts.py +0 -74
- schemathesis/service/metadata.py +0 -42
- schemathesis/service/models.py +0 -21
- schemathesis/service/serialization.py +0 -184
- schemathesis/service/worker.py +0 -39
- schemathesis/specs/graphql/loaders.py +0 -215
- schemathesis/specs/openapi/constants.py +0 -7
- schemathesis/specs/openapi/expressions/context.py +0 -12
- schemathesis/specs/openapi/expressions/pointers.py +0 -29
- schemathesis/specs/openapi/filters.py +0 -44
- schemathesis/specs/openapi/links.py +0 -303
- schemathesis/specs/openapi/loaders.py +0 -453
- schemathesis/specs/openapi/parameters.py +0 -430
- schemathesis/specs/openapi/security.py +0 -129
- schemathesis/specs/openapi/validation.py +0 -24
- schemathesis/stateful.py +0 -358
- schemathesis/targets.py +0 -32
- schemathesis/types.py +0 -38
- schemathesis/utils.py +0 -475
- schemathesis-3.15.4.dist-info/METADATA +0 -202
- schemathesis-3.15.4.dist-info/RECORD +0 -99
- schemathesis-3.15.4.dist-info/entry_points.txt +0 -7
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from os import PathLike
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import IO, TYPE_CHECKING, Any, Callable, Dict, NoReturn, TypeVar, cast
|
|
8
|
+
|
|
9
|
+
from schemathesis.config import SchemathesisConfig
|
|
10
|
+
from schemathesis.core.errors import LoaderError, LoaderErrorKind
|
|
11
|
+
from schemathesis.core.loaders import load_from_url, prepare_request_kwargs, raise_for_status, require_relative_url
|
|
12
|
+
from schemathesis.hooks import HookContext, dispatch
|
|
13
|
+
from schemathesis.python import asgi, wsgi
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from graphql import DocumentNode
|
|
17
|
+
|
|
18
|
+
from schemathesis.specs.graphql.schemas import GraphQLSchema
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def from_asgi(path: str, app: Any, *, config: SchemathesisConfig | None = None, **kwargs: Any) -> GraphQLSchema:
|
|
22
|
+
"""Load GraphQL schema from an ASGI application via introspection.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
path: Relative URL path to the GraphQL endpoint (e.g., "/graphql")
|
|
26
|
+
app: ASGI application instance
|
|
27
|
+
config: Custom configuration. If `None`, uses auto-discovered config
|
|
28
|
+
**kwargs: Additional request parameters passed to the ASGI test client.
|
|
29
|
+
|
|
30
|
+
Example:
|
|
31
|
+
```python
|
|
32
|
+
from fastapi import FastAPI
|
|
33
|
+
import schemathesis
|
|
34
|
+
|
|
35
|
+
app = FastAPI()
|
|
36
|
+
schema = schemathesis.graphql.from_asgi("/graphql", app)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
"""
|
|
40
|
+
require_relative_url(path)
|
|
41
|
+
kwargs.setdefault("json", {"query": get_introspection_query()})
|
|
42
|
+
client = asgi.get_client(app)
|
|
43
|
+
response = load_from_url(client.post, url=path, **kwargs)
|
|
44
|
+
schema = extract_schema_from_response(response, lambda r: r.json())
|
|
45
|
+
loaded = from_dict(schema=schema, config=config)
|
|
46
|
+
loaded.app = app
|
|
47
|
+
loaded.location = path
|
|
48
|
+
return loaded
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def from_wsgi(path: str, app: Any, *, config: SchemathesisConfig | None = None, **kwargs: Any) -> GraphQLSchema:
|
|
52
|
+
"""Load GraphQL schema from a WSGI application via introspection.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
path: Relative URL path to the GraphQL endpoint (e.g., "/graphql")
|
|
56
|
+
app: WSGI application instance
|
|
57
|
+
config: Custom configuration. If `None`, uses auto-discovered config
|
|
58
|
+
**kwargs: Additional request parameters passed to the WSGI test client.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
```python
|
|
62
|
+
from flask import Flask
|
|
63
|
+
import schemathesis
|
|
64
|
+
|
|
65
|
+
app = Flask(__name__)
|
|
66
|
+
schema = schemathesis.graphql.from_wsgi("/graphql", app)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
"""
|
|
70
|
+
require_relative_url(path)
|
|
71
|
+
prepare_request_kwargs(kwargs)
|
|
72
|
+
kwargs.setdefault("json", {"query": get_introspection_query()})
|
|
73
|
+
client = wsgi.get_client(app)
|
|
74
|
+
response = client.post(path=path, **kwargs)
|
|
75
|
+
raise_for_status(response)
|
|
76
|
+
schema = extract_schema_from_response(response, lambda r: r.json)
|
|
77
|
+
loaded = from_dict(schema=schema, config=config)
|
|
78
|
+
loaded.app = app
|
|
79
|
+
loaded.location = path
|
|
80
|
+
return loaded
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def from_url(
|
|
84
|
+
url: str, *, config: SchemathesisConfig | None = None, wait_for_schema: float | None = None, **kwargs: Any
|
|
85
|
+
) -> GraphQLSchema:
|
|
86
|
+
"""Load GraphQL schema from a URL via introspection query.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
url: Full URL to the GraphQL endpoint
|
|
90
|
+
config: Custom configuration. If `None`, uses auto-discovered config
|
|
91
|
+
wait_for_schema: Maximum time in seconds to wait for schema availability
|
|
92
|
+
**kwargs: Additional parameters passed to `requests.post()` (headers, timeout, auth, etc.).
|
|
93
|
+
|
|
94
|
+
Example:
|
|
95
|
+
```python
|
|
96
|
+
import schemathesis
|
|
97
|
+
|
|
98
|
+
# Basic usage
|
|
99
|
+
schema = schemathesis.graphql.from_url("https://api.example.com/graphql")
|
|
100
|
+
|
|
101
|
+
# With authentication and timeout
|
|
102
|
+
schema = schemathesis.graphql.from_url(
|
|
103
|
+
"https://api.example.com/graphql",
|
|
104
|
+
headers={"Authorization": "Bearer token"},
|
|
105
|
+
timeout=30,
|
|
106
|
+
wait_for_schema=10.0
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
"""
|
|
111
|
+
import requests
|
|
112
|
+
|
|
113
|
+
kwargs.setdefault("json", {"query": get_introspection_query()})
|
|
114
|
+
response = load_from_url(requests.post, url=url, wait_for_schema=wait_for_schema, **kwargs)
|
|
115
|
+
schema = extract_schema_from_response(response, lambda r: r.json())
|
|
116
|
+
loaded = from_dict(schema, config=config)
|
|
117
|
+
loaded.location = url
|
|
118
|
+
return loaded
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def from_path(
|
|
122
|
+
path: PathLike | str, *, config: SchemathesisConfig | None = None, encoding: str = "utf-8"
|
|
123
|
+
) -> GraphQLSchema:
|
|
124
|
+
"""Load GraphQL schema from a filesystem path.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
path: File path to the GraphQL schema file (.graphql, .gql)
|
|
128
|
+
config: Custom configuration. If `None`, uses auto-discovered config
|
|
129
|
+
encoding: Text encoding for reading the file
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
```python
|
|
133
|
+
import schemathesis
|
|
134
|
+
|
|
135
|
+
# Load from GraphQL SDL file
|
|
136
|
+
schema = schemathesis.graphql.from_path("./schema.graphql")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
"""
|
|
140
|
+
with open(path, encoding=encoding) as file:
|
|
141
|
+
loaded = from_file(file=file, config=config)
|
|
142
|
+
loaded.location = Path(path).absolute().as_uri()
|
|
143
|
+
return loaded
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def from_file(file: IO[str] | str, *, config: SchemathesisConfig | None = None) -> GraphQLSchema:
|
|
147
|
+
"""Load GraphQL schema from a file-like object or string.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
file: File-like object or raw string containing GraphQL SDL
|
|
151
|
+
config: Custom configuration. If `None`, uses auto-discovered config
|
|
152
|
+
|
|
153
|
+
Example:
|
|
154
|
+
```python
|
|
155
|
+
import schemathesis
|
|
156
|
+
|
|
157
|
+
# From GraphQL SDL string
|
|
158
|
+
schema_sdl = '''
|
|
159
|
+
type Query {
|
|
160
|
+
user(id: ID!): User
|
|
161
|
+
}
|
|
162
|
+
type User {
|
|
163
|
+
id: ID!
|
|
164
|
+
name: String!
|
|
165
|
+
}
|
|
166
|
+
'''
|
|
167
|
+
schema = schemathesis.graphql.from_file(schema_sdl)
|
|
168
|
+
|
|
169
|
+
# From file object
|
|
170
|
+
with open("schema.graphql") as f:
|
|
171
|
+
schema = schemathesis.graphql.from_file(f)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
import graphql
|
|
176
|
+
|
|
177
|
+
if isinstance(file, str):
|
|
178
|
+
data = file
|
|
179
|
+
else:
|
|
180
|
+
data = file.read()
|
|
181
|
+
try:
|
|
182
|
+
document = graphql.build_schema(data)
|
|
183
|
+
result = graphql.execute(document, get_introspection_query_ast())
|
|
184
|
+
# TYPES: We don't pass `is_awaitable` above, therefore `result` is of the `ExecutionResult` type
|
|
185
|
+
result = cast(graphql.ExecutionResult, result)
|
|
186
|
+
# TYPES:
|
|
187
|
+
# - `document` is a valid schema, because otherwise `build_schema` will rise an error;
|
|
188
|
+
# - `INTROSPECTION_QUERY` is a valid query - it is known upfront;
|
|
189
|
+
# Therefore the execution result is always valid at this point and `result.data` is not `None`
|
|
190
|
+
schema = cast(Dict[str, Any], result.data)
|
|
191
|
+
except Exception as exc:
|
|
192
|
+
try:
|
|
193
|
+
schema = json.loads(data)
|
|
194
|
+
if not isinstance(schema, dict) or "__schema" not in schema:
|
|
195
|
+
_on_invalid_schema(exc)
|
|
196
|
+
except json.JSONDecodeError:
|
|
197
|
+
_on_invalid_schema(exc, extras=[entry for entry in str(exc).splitlines() if entry])
|
|
198
|
+
return from_dict(schema, config=config)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def from_dict(schema: dict[str, Any], *, config: SchemathesisConfig | None = None) -> GraphQLSchema:
|
|
202
|
+
"""Load GraphQL schema from a dictionary containing introspection result.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
schema: Dictionary containing GraphQL introspection result or wrapped in 'data' key
|
|
206
|
+
config: Custom configuration. If `None`, uses auto-discovered config
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
```python
|
|
210
|
+
import schemathesis
|
|
211
|
+
|
|
212
|
+
# From introspection result
|
|
213
|
+
introspection = {
|
|
214
|
+
"__schema": {
|
|
215
|
+
"types": [...],
|
|
216
|
+
"queryType": {"name": "Query"},
|
|
217
|
+
# ... rest of introspection result
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
schema = schemathesis.graphql.from_dict(introspection)
|
|
221
|
+
|
|
222
|
+
# From GraphQL response format (with 'data' wrapper)
|
|
223
|
+
response_data = {
|
|
224
|
+
"data": {
|
|
225
|
+
"__schema": {
|
|
226
|
+
"types": [...],
|
|
227
|
+
"queryType": {"name": "Query"}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
schema = schemathesis.graphql.from_dict(response_data)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
"""
|
|
235
|
+
from schemathesis.specs.graphql.schemas import GraphQLSchema
|
|
236
|
+
|
|
237
|
+
if "data" in schema:
|
|
238
|
+
schema = schema["data"]
|
|
239
|
+
hook_context = HookContext()
|
|
240
|
+
dispatch("before_load_schema", hook_context, schema)
|
|
241
|
+
|
|
242
|
+
if config is None:
|
|
243
|
+
config = SchemathesisConfig.discover()
|
|
244
|
+
project_config = config.projects.get(schema)
|
|
245
|
+
instance = GraphQLSchema(schema, config=project_config)
|
|
246
|
+
instance.filter_set = project_config.operations.filter_set_with(include=instance.filter_set)
|
|
247
|
+
dispatch("after_load_schema", hook_context, instance)
|
|
248
|
+
return instance
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@lru_cache
|
|
252
|
+
def get_introspection_query() -> str:
|
|
253
|
+
import graphql
|
|
254
|
+
|
|
255
|
+
return graphql.get_introspection_query()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@lru_cache
|
|
259
|
+
def get_introspection_query_ast() -> DocumentNode:
|
|
260
|
+
import graphql
|
|
261
|
+
|
|
262
|
+
query = get_introspection_query()
|
|
263
|
+
return graphql.parse(query)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
R = TypeVar("R")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def extract_schema_from_response(response: R, callback: Callable[[R], Any]) -> dict[str, Any]:
|
|
270
|
+
try:
|
|
271
|
+
decoded = callback(response)
|
|
272
|
+
except json.JSONDecodeError as exc:
|
|
273
|
+
raise LoaderError(
|
|
274
|
+
LoaderErrorKind.UNEXPECTED_CONTENT_TYPE,
|
|
275
|
+
"Received unsupported content while expecting a JSON payload for GraphQL",
|
|
276
|
+
) from exc
|
|
277
|
+
return decoded
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _on_invalid_schema(exc: Exception, extras: list[str] | None = None) -> NoReturn:
|
|
281
|
+
raise LoaderError(
|
|
282
|
+
LoaderErrorKind.GRAPHQL_INVALID_SCHEMA,
|
|
283
|
+
"The provided API schema does not appear to be a valid GraphQL schema",
|
|
284
|
+
extras=extras or [],
|
|
285
|
+
) from exc
|