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
schemathesis/auth.py
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
"""Support for custom API authentication mechanisms."""
|
|
2
|
-
import threading
|
|
3
|
-
import time
|
|
4
|
-
from typing import TYPE_CHECKING, Any, Callable, Generic, Optional, Type, TypeVar
|
|
5
|
-
|
|
6
|
-
import attr
|
|
7
|
-
from typing_extensions import Protocol, runtime_checkable
|
|
8
|
-
|
|
9
|
-
from .exceptions import UsageError
|
|
10
|
-
from .types import GenericTest
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from .models import APIOperation, Case
|
|
14
|
-
|
|
15
|
-
DEFAULT_REFRESH_INTERVAL = 300
|
|
16
|
-
AUTH_STORAGE_ATTRIBUTE_NAME = "_schemathesis_auth"
|
|
17
|
-
Auth = TypeVar("Auth")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@attr.s(slots=True) # pragma: no mutate
|
|
21
|
-
class AuthContext:
|
|
22
|
-
"""Holds state relevant for the authentication process.
|
|
23
|
-
|
|
24
|
-
:ivar APIOperation operation: API operation that is currently being processed.
|
|
25
|
-
:ivar app: Optional Python application if the WSGI / ASGI integration is used.
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
operation: "APIOperation" = attr.ib() # pragma: no mutate
|
|
29
|
-
app: Optional[Any] = attr.ib() # pragma: no mutate
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@runtime_checkable
|
|
33
|
-
class AuthProvider(Protocol):
|
|
34
|
-
"""Get authentication data for an API and set it on the generated test cases."""
|
|
35
|
-
|
|
36
|
-
def get(self, context: AuthContext) -> Auth:
|
|
37
|
-
"""Get the authentication data.
|
|
38
|
-
|
|
39
|
-
:param AuthContext context: Holds state relevant for the authentication process.
|
|
40
|
-
:return: Any authentication data you find useful for your use case. For example, it could be an access token.
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
def set(self, case: "Case", data: Auth, context: AuthContext) -> None:
|
|
44
|
-
"""Set authentication data on a generated test case.
|
|
45
|
-
|
|
46
|
-
:param Optional[Auth] data: Authentication data you got from the ``get`` method.
|
|
47
|
-
:param Case case: Generated test case.
|
|
48
|
-
:param AuthContext context: Holds state relevant for the authentication process.
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
@attr.s(slots=True)
|
|
53
|
-
class CacheEntry(Generic[Auth]):
|
|
54
|
-
"""Cached auth data."""
|
|
55
|
-
|
|
56
|
-
data: Auth = attr.ib()
|
|
57
|
-
expires: float = attr.ib()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
@attr.s(slots=True)
|
|
61
|
-
class CachingAuthProvider(Generic[Auth]):
|
|
62
|
-
"""Caches the underlying auth provider."""
|
|
63
|
-
|
|
64
|
-
provider: AuthProvider = attr.ib()
|
|
65
|
-
refresh_interval: int = attr.ib(default=DEFAULT_REFRESH_INTERVAL)
|
|
66
|
-
cache_entry: Optional[CacheEntry[Auth]] = attr.ib(default=None)
|
|
67
|
-
# The timer exists here to simplify testing
|
|
68
|
-
timer: Callable[[], float] = attr.ib(default=time.monotonic)
|
|
69
|
-
_refresh_lock: threading.Lock = attr.ib(factory=threading.Lock)
|
|
70
|
-
|
|
71
|
-
def get(self, context: AuthContext) -> Auth:
|
|
72
|
-
"""Get cached auth value."""
|
|
73
|
-
if self.cache_entry is None or self.timer() >= self.cache_entry.expires:
|
|
74
|
-
with self._refresh_lock:
|
|
75
|
-
if not (self.cache_entry is None or self.timer() >= self.cache_entry.expires):
|
|
76
|
-
# Another thread updated the cache
|
|
77
|
-
return self.cache_entry.data
|
|
78
|
-
data: Auth = self.provider.get(context)
|
|
79
|
-
self.cache_entry = CacheEntry(data=data, expires=self.timer() + self.refresh_interval)
|
|
80
|
-
return data
|
|
81
|
-
return self.cache_entry.data
|
|
82
|
-
|
|
83
|
-
def set(self, case: "Case", data: Auth, context: AuthContext) -> None:
|
|
84
|
-
"""Set auth data on the `Case` instance.
|
|
85
|
-
|
|
86
|
-
This implementation delegates this to the actual provider.
|
|
87
|
-
"""
|
|
88
|
-
self.provider.set(case, data, context)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
@attr.s(slots=True)
|
|
92
|
-
class AuthStorage(Generic[Auth]):
|
|
93
|
-
"""Store and manage API authentication."""
|
|
94
|
-
|
|
95
|
-
provider: Optional[AuthProvider] = attr.ib(default=None)
|
|
96
|
-
|
|
97
|
-
@property
|
|
98
|
-
def is_defined(self) -> bool:
|
|
99
|
-
"""Whether there is an auth provider set."""
|
|
100
|
-
return self.provider is not None
|
|
101
|
-
|
|
102
|
-
def register(
|
|
103
|
-
self, refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL
|
|
104
|
-
) -> Callable[[Type[AuthProvider]], Type[AuthProvider]]:
|
|
105
|
-
"""Register a new auth provider.
|
|
106
|
-
|
|
107
|
-
.. code-block:: python
|
|
108
|
-
|
|
109
|
-
@schemathesis.auth.register()
|
|
110
|
-
class TokenAuth:
|
|
111
|
-
def get(self, context):
|
|
112
|
-
# This is a real endpoint, try it out!
|
|
113
|
-
response = requests.post(
|
|
114
|
-
"https://example.schemathesis.io/api/token/",
|
|
115
|
-
json={"username": "demo", "password": "test"},
|
|
116
|
-
)
|
|
117
|
-
data = response.json()
|
|
118
|
-
return data["access_token"]
|
|
119
|
-
|
|
120
|
-
def set(self, case, data, context):
|
|
121
|
-
# Modify `case` the way you need
|
|
122
|
-
case.headers = {"Authorization": f"Bearer {data}"}
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
def wrapper(provider_class: Type[AuthProvider]) -> Type[AuthProvider]:
|
|
126
|
-
if not issubclass(provider_class, AuthProvider):
|
|
127
|
-
raise TypeError(
|
|
128
|
-
f"`{provider_class.__name__}` is not a valid auth provider. "
|
|
129
|
-
f"Check `schemathesis.auth.AuthProvider` documentation for examples."
|
|
130
|
-
)
|
|
131
|
-
# Apply caching if desired
|
|
132
|
-
if refresh_interval is not None:
|
|
133
|
-
self.provider = CachingAuthProvider(provider_class(), refresh_interval=refresh_interval)
|
|
134
|
-
else:
|
|
135
|
-
self.provider = provider_class()
|
|
136
|
-
return provider_class
|
|
137
|
-
|
|
138
|
-
return wrapper
|
|
139
|
-
|
|
140
|
-
def unregister(self) -> None:
|
|
141
|
-
"""Unregister the currently registered auth provider.
|
|
142
|
-
|
|
143
|
-
No-op if there is no auth provider registered.
|
|
144
|
-
"""
|
|
145
|
-
self.provider = None
|
|
146
|
-
|
|
147
|
-
def apply(
|
|
148
|
-
self, provider_class: Type[AuthProvider], *, refresh_interval: Optional[int] = DEFAULT_REFRESH_INTERVAL
|
|
149
|
-
) -> Callable[[GenericTest], GenericTest]:
|
|
150
|
-
"""Register auth provider only on one test function.
|
|
151
|
-
|
|
152
|
-
:param Type[AuthProvider] provider_class: Authentication provider class.
|
|
153
|
-
:param Optional[int] refresh_interval: Cache duration in seconds.
|
|
154
|
-
|
|
155
|
-
.. code-block:: python
|
|
156
|
-
|
|
157
|
-
class Auth:
|
|
158
|
-
...
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
@schema.auth.apply(Auth)
|
|
162
|
-
@schema.parametrize()
|
|
163
|
-
def test_api(case):
|
|
164
|
-
...
|
|
165
|
-
|
|
166
|
-
"""
|
|
167
|
-
|
|
168
|
-
def wrapper(test: GenericTest) -> GenericTest:
|
|
169
|
-
auth_storage = self.add_auth_storage(test)
|
|
170
|
-
auth_storage.register(refresh_interval=refresh_interval)(provider_class)
|
|
171
|
-
return test
|
|
172
|
-
|
|
173
|
-
return wrapper
|
|
174
|
-
|
|
175
|
-
@classmethod
|
|
176
|
-
def add_auth_storage(cls, test: GenericTest) -> "AuthStorage":
|
|
177
|
-
"""Attach a new auth storage instance to the test if it is not already present."""
|
|
178
|
-
if not hasattr(test, AUTH_STORAGE_ATTRIBUTE_NAME):
|
|
179
|
-
setattr(test, AUTH_STORAGE_ATTRIBUTE_NAME, cls())
|
|
180
|
-
else:
|
|
181
|
-
raise UsageError(f"`{test.__name__}` has already been decorated with `apply`.")
|
|
182
|
-
return getattr(test, AUTH_STORAGE_ATTRIBUTE_NAME)
|
|
183
|
-
|
|
184
|
-
def set(self, case: "Case", context: AuthContext) -> None:
|
|
185
|
-
"""Set authentication data on a generated test case."""
|
|
186
|
-
if self.provider is not None:
|
|
187
|
-
data: Auth = self.provider.get(context)
|
|
188
|
-
self.provider.set(case, data, context)
|
|
189
|
-
else:
|
|
190
|
-
raise UsageError("No auth provider is defined.")
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def set_on_case(case: "Case", context: AuthContext, auth_storage: Optional[AuthStorage]) -> None:
|
|
194
|
-
"""Set authentication data on this case.
|
|
195
|
-
|
|
196
|
-
If there is no auth defined, then this function is no-op.
|
|
197
|
-
"""
|
|
198
|
-
if auth_storage is not None:
|
|
199
|
-
auth_storage.set(case, context)
|
|
200
|
-
elif case.operation.schema.auth.is_defined:
|
|
201
|
-
case.operation.schema.auth.set(case, context)
|
|
202
|
-
elif GLOBAL_AUTH_STORAGE.is_defined:
|
|
203
|
-
GLOBAL_AUTH_STORAGE.set(case, context)
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def get_auth_storage_from_test(test: GenericTest) -> Optional[AuthStorage]:
|
|
207
|
-
"""Extract the currently attached auth storage from a test function."""
|
|
208
|
-
return getattr(test, AUTH_STORAGE_ATTRIBUTE_NAME, None)
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
# Global auth API
|
|
212
|
-
GLOBAL_AUTH_STORAGE: AuthStorage = AuthStorage()
|
|
213
|
-
register = GLOBAL_AUTH_STORAGE.register
|
|
214
|
-
unregister = GLOBAL_AUTH_STORAGE.unregister
|
schemathesis/cli/callbacks.py
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import enum
|
|
2
|
-
import os
|
|
3
|
-
import re
|
|
4
|
-
from contextlib import contextmanager
|
|
5
|
-
from typing import Dict, Generator, List, Optional, Tuple, Union
|
|
6
|
-
from urllib.parse import urlparse
|
|
7
|
-
|
|
8
|
-
import click
|
|
9
|
-
import hypothesis
|
|
10
|
-
from requests import PreparedRequest, RequestException
|
|
11
|
-
|
|
12
|
-
from .. import utils
|
|
13
|
-
from ..constants import CodeSampleStyle, DataGenerationMethod
|
|
14
|
-
from ..stateful import Stateful
|
|
15
|
-
from .constants import DEFAULT_WORKERS
|
|
16
|
-
|
|
17
|
-
MISSING_CASSETTE_PATH_ARGUMENT_MESSAGE = (
|
|
18
|
-
'Missing argument, "--cassette-path" should be specified as well if you use "--cassette-preserve-exact-body-bytes".'
|
|
19
|
-
)
|
|
20
|
-
INVALID_SCHEMA_MESSAGE = "Invalid SCHEMA, must be a valid URL, file path or an API slug from Schemathesis.io."
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
@enum.unique
|
|
24
|
-
class SchemaInputKind(enum.Enum):
|
|
25
|
-
"""Kinds of SCHEMA input."""
|
|
26
|
-
|
|
27
|
-
# Regular URL like https://example.schemathesis.io/openapi.json
|
|
28
|
-
URL = 1
|
|
29
|
-
# Local path
|
|
30
|
-
PATH = 2
|
|
31
|
-
# Relative path within a Python app
|
|
32
|
-
APP_PATH = 3
|
|
33
|
-
# A short name for API created in Schemathesis.io
|
|
34
|
-
SLUG = 4
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def parse_schema_kind(schema: str, app: Optional[str]) -> SchemaInputKind:
|
|
38
|
-
"""Detect what kind the input schema is."""
|
|
39
|
-
try:
|
|
40
|
-
netloc = urlparse(schema).netloc
|
|
41
|
-
except ValueError as exc:
|
|
42
|
-
raise click.UsageError(INVALID_SCHEMA_MESSAGE) from exc
|
|
43
|
-
if "\x00" in schema or not schema:
|
|
44
|
-
raise click.UsageError(INVALID_SCHEMA_MESSAGE)
|
|
45
|
-
if netloc:
|
|
46
|
-
return SchemaInputKind.URL
|
|
47
|
-
if utils.file_exists(schema):
|
|
48
|
-
return SchemaInputKind.PATH
|
|
49
|
-
if app is not None:
|
|
50
|
-
return SchemaInputKind.APP_PATH
|
|
51
|
-
# Assume SLUG if it is not a URL or PATH or APP_PATH
|
|
52
|
-
return SchemaInputKind.SLUG
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def validate_schema(
|
|
56
|
-
schema: str,
|
|
57
|
-
kind: SchemaInputKind,
|
|
58
|
-
*,
|
|
59
|
-
base_url: Optional[str],
|
|
60
|
-
dry_run: bool,
|
|
61
|
-
app: Optional[str],
|
|
62
|
-
api_slug: Optional[str],
|
|
63
|
-
) -> None:
|
|
64
|
-
if kind == SchemaInputKind.URL:
|
|
65
|
-
validate_url(schema)
|
|
66
|
-
if kind == SchemaInputKind.PATH:
|
|
67
|
-
# Base URL is required if it is not a dry run
|
|
68
|
-
if app is None and base_url is None and not dry_run:
|
|
69
|
-
raise click.UsageError('Missing argument, "--base-url" is required for SCHEMA specified by file.')
|
|
70
|
-
if kind == SchemaInputKind.SLUG:
|
|
71
|
-
if api_slug is not None:
|
|
72
|
-
raise click.UsageError(f"Got unexpected extra argument ({api_slug})")
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def validate_url(value: str) -> None:
|
|
76
|
-
try:
|
|
77
|
-
PreparedRequest().prepare_url(value, {}) # type: ignore
|
|
78
|
-
except RequestException as exc:
|
|
79
|
-
raise click.UsageError(INVALID_SCHEMA_MESSAGE) from exc
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
def validate_base_url(ctx: click.core.Context, param: click.core.Parameter, raw_value: str) -> str:
|
|
83
|
-
try:
|
|
84
|
-
netloc = urlparse(raw_value).netloc
|
|
85
|
-
except ValueError as exc:
|
|
86
|
-
raise click.UsageError("Invalid base URL") from exc
|
|
87
|
-
if raw_value and not netloc:
|
|
88
|
-
raise click.UsageError("Invalid base URL")
|
|
89
|
-
return raw_value
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
APPLICATION_FORMAT_MESSAGE = (
|
|
93
|
-
"Can not import application from the given module!\n"
|
|
94
|
-
"The `--app` option value should be in format:\n\n path:variable\n\n"
|
|
95
|
-
"where `path` is an importable path to a Python module,\n"
|
|
96
|
-
"and `variable` is a variable name inside that module."
|
|
97
|
-
)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def validate_app(ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]) -> Optional[str]:
|
|
101
|
-
if raw_value is None:
|
|
102
|
-
return raw_value
|
|
103
|
-
try:
|
|
104
|
-
utils.import_app(raw_value)
|
|
105
|
-
# String is returned instead of an app because it might be passed to a subprocess
|
|
106
|
-
# Since most app instances are not-transferable to another process, they are passed as strings and
|
|
107
|
-
# imported in a subprocess
|
|
108
|
-
return raw_value
|
|
109
|
-
except Exception as exc:
|
|
110
|
-
show_errors_tracebacks = ctx.params["show_errors_tracebacks"]
|
|
111
|
-
message = utils.format_exception(exc, show_errors_tracebacks).strip()
|
|
112
|
-
click.secho(f"{APPLICATION_FORMAT_MESSAGE}\n\nException:\n\n{message}", fg="red")
|
|
113
|
-
if not show_errors_tracebacks:
|
|
114
|
-
click.secho(
|
|
115
|
-
"\nAdd this option to your command line parameters to see full tracebacks: --show-errors-tracebacks",
|
|
116
|
-
fg="red",
|
|
117
|
-
)
|
|
118
|
-
raise click.exceptions.Exit(1)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def validate_auth(
|
|
122
|
-
ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]
|
|
123
|
-
) -> Optional[Tuple[str, str]]:
|
|
124
|
-
if raw_value is not None:
|
|
125
|
-
with reraise_format_error(raw_value):
|
|
126
|
-
user, password = tuple(raw_value.split(":"))
|
|
127
|
-
if not user:
|
|
128
|
-
raise click.BadParameter("Username should not be empty")
|
|
129
|
-
if not utils.is_latin_1_encodable(user):
|
|
130
|
-
raise click.BadParameter("Username should be latin-1 encodable")
|
|
131
|
-
if not utils.is_latin_1_encodable(password):
|
|
132
|
-
raise click.BadParameter("Password should be latin-1 encodable")
|
|
133
|
-
return user, password
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def validate_headers(
|
|
138
|
-
ctx: click.core.Context, param: click.core.Parameter, raw_value: Tuple[str, ...]
|
|
139
|
-
) -> Dict[str, str]:
|
|
140
|
-
headers = {}
|
|
141
|
-
for header in raw_value:
|
|
142
|
-
with reraise_format_error(header):
|
|
143
|
-
key, value = header.split(":", maxsplit=1)
|
|
144
|
-
value = value.lstrip()
|
|
145
|
-
key = key.strip()
|
|
146
|
-
if not key:
|
|
147
|
-
raise click.BadParameter("Header name should not be empty")
|
|
148
|
-
if not utils.is_latin_1_encodable(key):
|
|
149
|
-
raise click.BadParameter("Header name should be latin-1 encodable")
|
|
150
|
-
if not utils.is_latin_1_encodable(value):
|
|
151
|
-
raise click.BadParameter("Header value should be latin-1 encodable")
|
|
152
|
-
if utils.has_invalid_characters(key, value):
|
|
153
|
-
raise click.BadParameter("Invalid return character or leading space in header")
|
|
154
|
-
headers[key] = value
|
|
155
|
-
return headers
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def validate_regex(ctx: click.core.Context, param: click.core.Parameter, raw_value: Tuple[str, ...]) -> Tuple[str, ...]:
|
|
159
|
-
for value in raw_value:
|
|
160
|
-
try:
|
|
161
|
-
re.compile(value)
|
|
162
|
-
except (re.error, OverflowError, RuntimeError) as exc:
|
|
163
|
-
raise click.BadParameter(f"Invalid regex: {exc.args[0]}")
|
|
164
|
-
return raw_value
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def validate_request_cert_key(
|
|
168
|
-
ctx: click.core.Context, param: click.core.Parameter, raw_value: Optional[str]
|
|
169
|
-
) -> Optional[str]:
|
|
170
|
-
if raw_value is not None and "request_cert" not in ctx.params:
|
|
171
|
-
raise click.UsageError('Missing argument, "--request-cert" should be specified as well.')
|
|
172
|
-
return raw_value
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def validate_preserve_exact_body_bytes(ctx: click.core.Context, param: click.core.Parameter, raw_value: bool) -> bool:
|
|
176
|
-
if raw_value and ctx.params["cassette_path"] is None:
|
|
177
|
-
raise click.UsageError(MISSING_CASSETTE_PATH_ARGUMENT_MESSAGE)
|
|
178
|
-
return raw_value
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
def convert_verbosity(
|
|
182
|
-
ctx: click.core.Context, param: click.core.Parameter, value: Optional[str]
|
|
183
|
-
) -> Optional[hypothesis.Verbosity]:
|
|
184
|
-
if value is None:
|
|
185
|
-
return value
|
|
186
|
-
return hypothesis.Verbosity[value]
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
def convert_stateful(ctx: click.core.Context, param: click.core.Parameter, value: str) -> Optional[Stateful]:
|
|
190
|
-
if value == "none":
|
|
191
|
-
return None
|
|
192
|
-
return Stateful[value]
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
def convert_checks(ctx: click.core.Context, param: click.core.Parameter, value: Tuple[List[str]]) -> List[str]:
|
|
196
|
-
return sum(value, [])
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
def convert_code_sample_style(ctx: click.core.Context, param: click.core.Parameter, value: str) -> CodeSampleStyle:
|
|
200
|
-
return CodeSampleStyle.from_str(value)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def convert_data_generation_method(
|
|
204
|
-
ctx: click.core.Context, param: click.core.Parameter, value: str
|
|
205
|
-
) -> List[DataGenerationMethod]:
|
|
206
|
-
return [DataGenerationMethod[value]]
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def convert_request_tls_verify(ctx: click.core.Context, param: click.core.Parameter, value: str) -> Union[str, bool]:
|
|
210
|
-
if value.lower() in ("y", "yes", "t", "true", "on", "1"):
|
|
211
|
-
return True
|
|
212
|
-
if value.lower() in ("n", "no", "f", "false", "off", "0"):
|
|
213
|
-
return False
|
|
214
|
-
return value
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
@contextmanager
|
|
218
|
-
def reraise_format_error(raw_value: str) -> Generator[None, None, None]:
|
|
219
|
-
try:
|
|
220
|
-
yield
|
|
221
|
-
except ValueError as exc:
|
|
222
|
-
raise click.BadParameter(f"Should be in KEY:VALUE format. Got: {raw_value}") from exc
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
def get_workers_count() -> int:
|
|
226
|
-
"""Detect the number of available CPUs for the current process, if possible.
|
|
227
|
-
|
|
228
|
-
Use ``DEFAULT_WORKERS`` if not possible to detect.
|
|
229
|
-
"""
|
|
230
|
-
if hasattr(os, "sched_getaffinity"):
|
|
231
|
-
# In contrast with `os.cpu_count` this call respects limits on CPU resources on some Unix systems
|
|
232
|
-
return len(os.sched_getaffinity(0))
|
|
233
|
-
# Number of CPUs in the system, or 1 if undetermined
|
|
234
|
-
return os.cpu_count() or DEFAULT_WORKERS
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
def convert_workers(ctx: click.core.Context, param: click.core.Parameter, value: str) -> int:
|
|
238
|
-
if value == "auto":
|
|
239
|
-
return get_workers_count()
|
|
240
|
-
return int(value)
|