schemathesis 3.25.6__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.
- schemathesis/__init__.py +6 -6
- schemathesis/_compat.py +2 -2
- schemathesis/_dependency_versions.py +4 -2
- schemathesis/_hypothesis.py +369 -56
- schemathesis/_lazy_import.py +1 -0
- schemathesis/_override.py +5 -4
- schemathesis/_patches.py +21 -0
- schemathesis/_rate_limiter.py +7 -0
- schemathesis/_xml.py +75 -22
- schemathesis/auths.py +78 -16
- schemathesis/checks.py +21 -9
- schemathesis/cli/__init__.py +783 -432
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/callbacks.py +58 -13
- schemathesis/cli/cassettes.py +233 -47
- schemathesis/cli/constants.py +8 -2
- schemathesis/cli/context.py +22 -5
- schemathesis/cli/debug.py +2 -1
- schemathesis/cli/handlers.py +4 -1
- schemathesis/cli/junitxml.py +103 -22
- schemathesis/cli/options.py +15 -4
- schemathesis/cli/output/default.py +258 -112
- schemathesis/cli/output/short.py +23 -8
- schemathesis/cli/reporting.py +79 -0
- schemathesis/cli/sanitization.py +6 -0
- schemathesis/code_samples.py +5 -3
- schemathesis/constants.py +1 -0
- schemathesis/contrib/openapi/__init__.py +1 -1
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -1
- schemathesis/contrib/openapi/formats/uuid.py +2 -1
- schemathesis/contrib/unique_data.py +3 -3
- schemathesis/exceptions.py +76 -65
- schemathesis/experimental/__init__.py +35 -0
- schemathesis/extra/_aiohttp.py +1 -0
- schemathesis/extra/_flask.py +4 -1
- schemathesis/extra/_server.py +1 -0
- schemathesis/extra/pytest_plugin.py +17 -25
- schemathesis/failures.py +77 -9
- schemathesis/filters.py +185 -8
- schemathesis/fixups/__init__.py +1 -0
- schemathesis/fixups/fast_api.py +2 -2
- schemathesis/fixups/utf8_bom.py +1 -2
- schemathesis/generation/__init__.py +20 -36
- schemathesis/generation/_hypothesis.py +59 -0
- schemathesis/generation/_methods.py +44 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/graphql.py +0 -1
- schemathesis/hooks.py +89 -12
- schemathesis/internal/checks.py +84 -0
- schemathesis/internal/copy.py +22 -3
- schemathesis/internal/deprecation.py +6 -2
- schemathesis/internal/diff.py +15 -0
- schemathesis/internal/extensions.py +27 -0
- schemathesis/internal/jsonschema.py +2 -1
- schemathesis/internal/output.py +68 -0
- schemathesis/internal/result.py +1 -1
- schemathesis/internal/transformation.py +11 -0
- schemathesis/lazy.py +138 -25
- schemathesis/loaders.py +7 -5
- schemathesis/models.py +318 -211
- schemathesis/parameters.py +4 -0
- schemathesis/runner/__init__.py +50 -15
- schemathesis/runner/events.py +65 -5
- schemathesis/runner/impl/context.py +104 -0
- schemathesis/runner/impl/core.py +388 -177
- schemathesis/runner/impl/solo.py +19 -29
- schemathesis/runner/impl/threadpool.py +70 -79
- schemathesis/runner/probes.py +11 -9
- schemathesis/runner/serialization.py +150 -17
- schemathesis/sanitization.py +5 -1
- schemathesis/schemas.py +170 -102
- schemathesis/serializers.py +7 -2
- schemathesis/service/ci.py +1 -0
- schemathesis/service/client.py +39 -6
- schemathesis/service/events.py +5 -1
- schemathesis/service/extensions.py +224 -0
- schemathesis/service/hosts.py +6 -2
- schemathesis/service/metadata.py +25 -0
- schemathesis/service/models.py +211 -2
- schemathesis/service/report.py +6 -6
- schemathesis/service/serialization.py +45 -71
- schemathesis/service/usage.py +1 -0
- schemathesis/specs/graphql/_cache.py +26 -0
- schemathesis/specs/graphql/loaders.py +25 -5
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +2 -2
- schemathesis/specs/graphql/schemas.py +130 -100
- schemathesis/specs/graphql/validation.py +1 -2
- schemathesis/specs/openapi/__init__.py +1 -0
- schemathesis/specs/openapi/_cache.py +123 -0
- schemathesis/specs/openapi/_hypothesis.py +78 -60
- schemathesis/specs/openapi/checks.py +504 -25
- schemathesis/specs/openapi/converter.py +31 -4
- schemathesis/specs/openapi/definitions.py +10 -17
- schemathesis/specs/openapi/examples.py +126 -12
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +1 -1
- schemathesis/specs/openapi/expressions/extractors.py +26 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +29 -6
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +44 -0
- schemathesis/specs/openapi/links.py +125 -42
- schemathesis/specs/openapi/loaders.py +77 -36
- schemathesis/specs/openapi/media_types.py +34 -0
- schemathesis/specs/openapi/negative/__init__.py +6 -3
- schemathesis/specs/openapi/negative/mutations.py +21 -6
- schemathesis/specs/openapi/parameters.py +39 -25
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +37 -7
- schemathesis/specs/openapi/schemas.py +360 -241
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +1 -0
- schemathesis/specs/openapi/stateful/__init__.py +198 -70
- schemathesis/specs/openapi/stateful/statistic.py +198 -0
- schemathesis/specs/openapi/stateful/types.py +14 -0
- schemathesis/specs/openapi/utils.py +6 -1
- schemathesis/specs/openapi/validation.py +1 -0
- schemathesis/stateful/__init__.py +35 -21
- schemathesis/stateful/config.py +97 -0
- schemathesis/stateful/context.py +135 -0
- schemathesis/stateful/events.py +274 -0
- schemathesis/stateful/runner.py +309 -0
- schemathesis/stateful/sink.py +68 -0
- schemathesis/stateful/state_machine.py +67 -38
- schemathesis/stateful/statistic.py +22 -0
- schemathesis/stateful/validation.py +100 -0
- schemathesis/targets.py +33 -1
- schemathesis/throttling.py +25 -5
- schemathesis/transports/__init__.py +354 -0
- schemathesis/transports/asgi.py +7 -0
- schemathesis/transports/auth.py +25 -2
- schemathesis/transports/content_types.py +3 -1
- schemathesis/transports/headers.py +2 -1
- schemathesis/transports/responses.py +9 -4
- schemathesis/types.py +9 -0
- schemathesis/utils.py +11 -16
- schemathesis-3.39.7.dist-info/METADATA +293 -0
- schemathesis-3.39.7.dist-info/RECORD +160 -0
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/WHEEL +1 -1
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis-3.25.6.dist-info/METADATA +0 -356
- schemathesis-3.25.6.dist-info/RECORD +0 -134
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,75 +1,43 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
4
|
from contextlib import suppress
|
|
5
5
|
from dataclasses import dataclass
|
|
6
|
-
from functools import lru_cache
|
|
7
6
|
from typing import Any, Callable, Dict, Iterable, Optional
|
|
8
7
|
from urllib.parse import quote_plus
|
|
9
8
|
from weakref import WeakKeyDictionary
|
|
10
9
|
|
|
11
|
-
from hypothesis import
|
|
10
|
+
from hypothesis import reject
|
|
11
|
+
from hypothesis import strategies as st
|
|
12
12
|
from hypothesis_jsonschema import from_schema
|
|
13
|
-
from requests.auth import _basic_auth_str
|
|
14
13
|
from requests.structures import CaseInsensitiveDict
|
|
15
14
|
from requests.utils import to_key_val_list
|
|
16
15
|
|
|
16
|
+
from ... import auths, serializers
|
|
17
17
|
from ..._hypothesis import prepare_urlencoded
|
|
18
18
|
from ...constants import NOT_SET
|
|
19
|
-
from
|
|
20
|
-
from ... import auths, serializers
|
|
19
|
+
from ...exceptions import BodyInGetRequestError, SerializationNotPossible
|
|
21
20
|
from ...generation import DataGenerationMethod, GenerationConfig
|
|
22
|
-
from ...internal.copy import fast_deepcopy
|
|
23
|
-
from ...exceptions import SerializationNotPossible, BodyInGetRequestError
|
|
24
21
|
from ...hooks import HookContext, HookDispatcher, apply_to_all_dispatchers
|
|
22
|
+
from ...internal.copy import fast_deepcopy
|
|
25
23
|
from ...internal.validation import is_illegal_surrogate
|
|
26
|
-
from ...models import APIOperation, Case, cant_serialize
|
|
24
|
+
from ...models import APIOperation, Case, GenerationMetadata, TestPhase, cant_serialize
|
|
27
25
|
from ...transports.content_types import parse_content_type
|
|
28
26
|
from ...transports.headers import has_invalid_characters, is_latin_1_encodable
|
|
29
27
|
from ...types import NotSet
|
|
30
|
-
from ...
|
|
31
|
-
from ...utils import compose, skip
|
|
28
|
+
from ...utils import skip
|
|
32
29
|
from .constants import LOCATION_TO_CONTAINER
|
|
30
|
+
from .formats import HEADER_FORMAT, STRING_FORMATS, get_default_format_strategies, header_values
|
|
31
|
+
from .media_types import MEDIA_TYPES
|
|
33
32
|
from .negative import negative_schema
|
|
34
33
|
from .negative.utils import can_negate
|
|
35
|
-
from .parameters import OpenAPIBody, parameters_to_json_schema
|
|
34
|
+
from .parameters import OpenAPIBody, OpenAPIParameter, parameters_to_json_schema
|
|
36
35
|
from .utils import is_header_location
|
|
37
36
|
|
|
38
|
-
HEADER_FORMAT = "_header_value"
|
|
39
37
|
SLASH = "/"
|
|
40
38
|
StrategyFactory = Callable[[Dict[str, Any], str, str, Optional[str], GenerationConfig], st.SearchStrategy]
|
|
41
39
|
|
|
42
40
|
|
|
43
|
-
def header_values(blacklist_characters: str = "\n\r") -> st.SearchStrategy[str]:
|
|
44
|
-
return st.text(alphabet=st.characters(min_codepoint=0, max_codepoint=255, blacklist_characters="\n\r"))
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@lru_cache
|
|
48
|
-
def get_default_format_strategies() -> dict[str, st.SearchStrategy]:
|
|
49
|
-
"""Get all default "format" strategies."""
|
|
50
|
-
|
|
51
|
-
def make_basic_auth_str(item: tuple[str, str]) -> str:
|
|
52
|
-
return _basic_auth_str(*item)
|
|
53
|
-
|
|
54
|
-
latin1_text = st.text(alphabet=st.characters(min_codepoint=0, max_codepoint=255))
|
|
55
|
-
|
|
56
|
-
# Define valid characters here to avoid filtering them out in `is_valid_header` later
|
|
57
|
-
header_value = header_values()
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
"binary": st.binary().map(Binary),
|
|
61
|
-
"byte": st.binary().map(lambda x: b64encode(x).decode()),
|
|
62
|
-
# RFC 7230, Section 3.2.6
|
|
63
|
-
"_header_name": st.text(
|
|
64
|
-
min_size=1, alphabet=st.sampled_from("!#$%&'*+-.^_`|~" + string.digits + string.ascii_letters)
|
|
65
|
-
),
|
|
66
|
-
# Header values with leading non-visible chars can't be sent with `requests`
|
|
67
|
-
HEADER_FORMAT: header_value.map(str.lstrip),
|
|
68
|
-
"_basic_auth": st.tuples(latin1_text, latin1_text).map(make_basic_auth_str),
|
|
69
|
-
"_bearer_auth": header_value.map("Bearer {}".format),
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
41
|
def is_valid_header(headers: dict[str, Any]) -> bool:
|
|
74
42
|
"""Verify if the generated headers are valid."""
|
|
75
43
|
for name, value in headers.items():
|
|
@@ -117,6 +85,7 @@ def get_case_strategy(
|
|
|
117
85
|
body: Any = NOT_SET,
|
|
118
86
|
media_type: str | None = None,
|
|
119
87
|
skip_on_not_negated: bool = True,
|
|
88
|
+
phase: TestPhase = TestPhase.GENERATE,
|
|
120
89
|
) -> Any:
|
|
121
90
|
"""A strategy that creates `Case` instances.
|
|
122
91
|
|
|
@@ -130,6 +99,7 @@ def get_case_strategy(
|
|
|
130
99
|
The primary purpose of this behavior is to prevent sending incomplete explicit examples by generating missing parts
|
|
131
100
|
as it works with `body`.
|
|
132
101
|
"""
|
|
102
|
+
start = time.monotonic()
|
|
133
103
|
strategy_factory = DATA_GENERATION_METHOD_TO_STRATEGY_FACTORY[generator]
|
|
134
104
|
|
|
135
105
|
context = HookContext(operation)
|
|
@@ -175,6 +145,11 @@ def get_case_strategy(
|
|
|
175
145
|
else:
|
|
176
146
|
body_ = ValueContainer(value=body, location="body", generator=None)
|
|
177
147
|
else:
|
|
148
|
+
# This explicit body payload comes for a media type that has a custom strategy registered
|
|
149
|
+
# Such strategies only support binary payloads, otherwise they can't be serialized
|
|
150
|
+
if not isinstance(body, bytes) and media_type in MEDIA_TYPES:
|
|
151
|
+
all_media_types = operation.get_request_payload_content_types()
|
|
152
|
+
raise SerializationNotPossible.from_media_types(*all_media_types)
|
|
178
153
|
body_ = ValueContainer(value=body, location="body", generator=None)
|
|
179
154
|
|
|
180
155
|
if operation.schema.validate_schema and operation.method.upper() == "GET" and operation.body:
|
|
@@ -187,6 +162,7 @@ def get_case_strategy(
|
|
|
187
162
|
reject()
|
|
188
163
|
instance = Case(
|
|
189
164
|
operation=operation,
|
|
165
|
+
generation_time=time.monotonic() - start,
|
|
190
166
|
media_type=media_type,
|
|
191
167
|
path_parameters=path_parameters_.value,
|
|
192
168
|
headers=CaseInsensitiveDict(headers_.value) if headers_.value is not None else headers_.value,
|
|
@@ -194,6 +170,18 @@ def get_case_strategy(
|
|
|
194
170
|
query=query_.value,
|
|
195
171
|
body=body_.value,
|
|
196
172
|
data_generation_method=generator,
|
|
173
|
+
meta=GenerationMetadata(
|
|
174
|
+
query=query_.generator,
|
|
175
|
+
path_parameters=path_parameters_.generator,
|
|
176
|
+
headers=headers_.generator,
|
|
177
|
+
cookies=cookies_.generator,
|
|
178
|
+
body=body_.generator,
|
|
179
|
+
phase=phase,
|
|
180
|
+
description=None,
|
|
181
|
+
location=None,
|
|
182
|
+
parameter=None,
|
|
183
|
+
parameter_location=None,
|
|
184
|
+
),
|
|
197
185
|
)
|
|
198
186
|
auth_context = auths.AuthContext(
|
|
199
187
|
operation=operation,
|
|
@@ -212,6 +200,8 @@ def _get_body_strategy(
|
|
|
212
200
|
operation: APIOperation,
|
|
213
201
|
generation_config: GenerationConfig,
|
|
214
202
|
) -> st.SearchStrategy:
|
|
203
|
+
if parameter.media_type in MEDIA_TYPES:
|
|
204
|
+
return MEDIA_TYPES[parameter.media_type]
|
|
215
205
|
# The cache key relies on object ids, which means that the parameter should not be mutated
|
|
216
206
|
# Note, the parent schema is not included as each parameter belong only to one schema
|
|
217
207
|
if parameter in _BODY_STRATEGIES_CACHE and strategy_factory in _BODY_STRATEGIES_CACHE[parameter]:
|
|
@@ -265,6 +255,8 @@ class ValueContainer:
|
|
|
265
255
|
location: str
|
|
266
256
|
generator: DataGenerationMethod | None
|
|
267
257
|
|
|
258
|
+
__slots__ = ("value", "location", "generator")
|
|
259
|
+
|
|
268
260
|
@property
|
|
269
261
|
def is_generated(self) -> bool:
|
|
270
262
|
"""If value was generated."""
|
|
@@ -332,6 +324,22 @@ def can_negate_headers(operation: APIOperation, location: str) -> bool:
|
|
|
332
324
|
return any(header != {"type": "string"} for header in headers.values())
|
|
333
325
|
|
|
334
326
|
|
|
327
|
+
def get_schema_for_location(
|
|
328
|
+
operation: APIOperation, location: str, parameters: Iterable[OpenAPIParameter]
|
|
329
|
+
) -> dict[str, Any]:
|
|
330
|
+
schema = parameters_to_json_schema(operation, parameters)
|
|
331
|
+
if location == "path":
|
|
332
|
+
if not operation.schema.validate_schema:
|
|
333
|
+
# If schema validation is disabled, we try to generate data even if the parameter definition
|
|
334
|
+
# contains errors.
|
|
335
|
+
# In this case, we know that the `required` keyword should always be `True`.
|
|
336
|
+
schema["required"] = list(schema["properties"])
|
|
337
|
+
for prop in schema.get("properties", {}).values():
|
|
338
|
+
if prop.get("type") == "string":
|
|
339
|
+
prop.setdefault("minLength", 1)
|
|
340
|
+
return operation.schema.prepare_schema(schema)
|
|
341
|
+
|
|
342
|
+
|
|
335
343
|
def get_parameters_strategy(
|
|
336
344
|
operation: APIOperation,
|
|
337
345
|
strategy_factory: StrategyFactory,
|
|
@@ -346,13 +354,7 @@ def get_parameters_strategy(
|
|
|
346
354
|
nested_cache_key = (strategy_factory, location, tuple(sorted(exclude)))
|
|
347
355
|
if operation in _PARAMETER_STRATEGIES_CACHE and nested_cache_key in _PARAMETER_STRATEGIES_CACHE[operation]:
|
|
348
356
|
return _PARAMETER_STRATEGIES_CACHE[operation][nested_cache_key]
|
|
349
|
-
schema =
|
|
350
|
-
if not operation.schema.validate_schema and location == "path":
|
|
351
|
-
# If schema validation is disabled, we try to generate data even if the parameter definition
|
|
352
|
-
# contains errors.
|
|
353
|
-
# In this case, we know that the `required` keyword should always be `True`.
|
|
354
|
-
schema["required"] = list(schema["properties"])
|
|
355
|
-
schema = operation.schema.prepare_schema(schema)
|
|
357
|
+
schema = get_schema_for_location(operation, location, parameters)
|
|
356
358
|
for name in exclude:
|
|
357
359
|
# Values from `exclude` are not necessarily valid for the schema - they come from user-defined examples
|
|
358
360
|
# that may be invalid
|
|
@@ -380,12 +382,10 @@ def get_parameters_strategy(
|
|
|
380
382
|
# `True` / `False` / `None` improves chances of them passing validation in apps
|
|
381
383
|
# that expect boolean / null types
|
|
382
384
|
# and not aware of Python-specific representation of those types
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
if map_func:
|
|
388
|
-
strategy = strategy.map(map_func) # type: ignore
|
|
385
|
+
if location == "path":
|
|
386
|
+
strategy = strategy.map(quote_all).map(jsonify_python_specific_types)
|
|
387
|
+
elif location == "query":
|
|
388
|
+
strategy = strategy.map(jsonify_python_specific_types)
|
|
389
389
|
_PARAMETER_STRATEGIES_CACHE.setdefault(operation, {})[nested_cache_key] = strategy
|
|
390
390
|
return strategy
|
|
391
391
|
# No parameters defined for this location
|
|
@@ -412,6 +412,17 @@ def jsonify_python_specific_types(value: dict[str, Any]) -> dict[str, Any]:
|
|
|
412
412
|
return value
|
|
413
413
|
|
|
414
414
|
|
|
415
|
+
def _build_custom_formats(
|
|
416
|
+
custom_formats: dict[str, st.SearchStrategy] | None, generation_config: GenerationConfig
|
|
417
|
+
) -> dict[str, st.SearchStrategy]:
|
|
418
|
+
custom_formats = {**get_default_format_strategies(), **STRING_FORMATS, **(custom_formats or {})}
|
|
419
|
+
if generation_config.headers.strategy is not None:
|
|
420
|
+
custom_formats[HEADER_FORMAT] = generation_config.headers.strategy
|
|
421
|
+
elif not generation_config.allow_x00:
|
|
422
|
+
custom_formats[HEADER_FORMAT] = header_values(blacklist_characters="\n\r\x00")
|
|
423
|
+
return custom_formats
|
|
424
|
+
|
|
425
|
+
|
|
415
426
|
def make_positive_strategy(
|
|
416
427
|
schema: dict[str, Any],
|
|
417
428
|
operation_name: str,
|
|
@@ -428,9 +439,10 @@ def make_positive_strategy(
|
|
|
428
439
|
for sub_schema in schema.get("properties", {}).values():
|
|
429
440
|
if list(sub_schema) == ["type"] and sub_schema["type"] == "string":
|
|
430
441
|
sub_schema.setdefault("format", HEADER_FORMAT)
|
|
442
|
+
custom_formats = _build_custom_formats(custom_formats, generation_config)
|
|
431
443
|
return from_schema(
|
|
432
444
|
schema,
|
|
433
|
-
custom_formats=
|
|
445
|
+
custom_formats=custom_formats,
|
|
434
446
|
allow_x00=generation_config.allow_x00,
|
|
435
447
|
codec=generation_config.codec,
|
|
436
448
|
)
|
|
@@ -449,12 +461,13 @@ def make_negative_strategy(
|
|
|
449
461
|
generation_config: GenerationConfig,
|
|
450
462
|
custom_formats: dict[str, st.SearchStrategy] | None = None,
|
|
451
463
|
) -> st.SearchStrategy:
|
|
464
|
+
custom_formats = _build_custom_formats(custom_formats, generation_config)
|
|
452
465
|
return negative_schema(
|
|
453
466
|
schema,
|
|
454
467
|
operation_name=operation_name,
|
|
455
468
|
location=location,
|
|
456
469
|
media_type=media_type,
|
|
457
|
-
custom_formats=
|
|
470
|
+
custom_formats=custom_formats,
|
|
458
471
|
generation_config=generation_config,
|
|
459
472
|
)
|
|
460
473
|
|
|
@@ -478,7 +491,12 @@ def is_valid_path(parameters: dict[str, Any]) -> bool:
|
|
|
478
491
|
disallowed_values = (SLASH, "")
|
|
479
492
|
|
|
480
493
|
return not any(
|
|
481
|
-
(
|
|
494
|
+
(
|
|
495
|
+
value in disallowed_values
|
|
496
|
+
or is_illegal_surrogate(value)
|
|
497
|
+
or isinstance(value, str)
|
|
498
|
+
and (SLASH in value or "}" in value or "{" in value)
|
|
499
|
+
)
|
|
482
500
|
for value in parameters.values()
|
|
483
501
|
)
|
|
484
502
|
|