schemathesis 4.1.4__py3-none-any.whl → 4.2.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.
Files changed (70) hide show
  1. schemathesis/cli/commands/run/executor.py +1 -1
  2. schemathesis/cli/commands/run/handlers/base.py +28 -1
  3. schemathesis/cli/commands/run/handlers/cassettes.py +109 -137
  4. schemathesis/cli/commands/run/handlers/junitxml.py +5 -6
  5. schemathesis/cli/commands/run/handlers/output.py +7 -1
  6. schemathesis/cli/ext/fs.py +1 -1
  7. schemathesis/config/_diff_base.py +3 -1
  8. schemathesis/config/_operations.py +2 -0
  9. schemathesis/config/_phases.py +21 -4
  10. schemathesis/config/_projects.py +10 -2
  11. schemathesis/core/adapter.py +34 -0
  12. schemathesis/core/errors.py +29 -5
  13. schemathesis/core/jsonschema/__init__.py +13 -0
  14. schemathesis/core/jsonschema/bundler.py +163 -0
  15. schemathesis/{specs/openapi/constants.py → core/jsonschema/keywords.py} +0 -8
  16. schemathesis/core/jsonschema/references.py +122 -0
  17. schemathesis/core/jsonschema/types.py +41 -0
  18. schemathesis/core/media_types.py +6 -4
  19. schemathesis/core/parameters.py +37 -0
  20. schemathesis/core/transforms.py +25 -2
  21. schemathesis/core/validation.py +19 -0
  22. schemathesis/engine/context.py +1 -1
  23. schemathesis/engine/errors.py +11 -18
  24. schemathesis/engine/phases/stateful/_executor.py +1 -1
  25. schemathesis/engine/phases/unit/_executor.py +30 -13
  26. schemathesis/errors.py +4 -0
  27. schemathesis/filters.py +2 -2
  28. schemathesis/generation/coverage.py +87 -11
  29. schemathesis/generation/hypothesis/__init__.py +79 -2
  30. schemathesis/generation/hypothesis/builder.py +108 -70
  31. schemathesis/generation/meta.py +5 -14
  32. schemathesis/generation/overrides.py +17 -17
  33. schemathesis/pytest/lazy.py +1 -1
  34. schemathesis/pytest/plugin.py +1 -6
  35. schemathesis/schemas.py +22 -72
  36. schemathesis/specs/graphql/schemas.py +27 -16
  37. schemathesis/specs/openapi/_hypothesis.py +83 -68
  38. schemathesis/specs/openapi/adapter/__init__.py +10 -0
  39. schemathesis/specs/openapi/adapter/parameters.py +504 -0
  40. schemathesis/specs/openapi/adapter/protocol.py +57 -0
  41. schemathesis/specs/openapi/adapter/references.py +19 -0
  42. schemathesis/specs/openapi/adapter/responses.py +329 -0
  43. schemathesis/specs/openapi/adapter/security.py +141 -0
  44. schemathesis/specs/openapi/adapter/v2.py +28 -0
  45. schemathesis/specs/openapi/adapter/v3_0.py +28 -0
  46. schemathesis/specs/openapi/adapter/v3_1.py +28 -0
  47. schemathesis/specs/openapi/checks.py +99 -90
  48. schemathesis/specs/openapi/converter.py +114 -27
  49. schemathesis/specs/openapi/examples.py +210 -168
  50. schemathesis/specs/openapi/negative/__init__.py +12 -7
  51. schemathesis/specs/openapi/negative/mutations.py +68 -40
  52. schemathesis/specs/openapi/references.py +2 -175
  53. schemathesis/specs/openapi/schemas.py +142 -490
  54. schemathesis/specs/openapi/serialization.py +15 -7
  55. schemathesis/specs/openapi/stateful/__init__.py +17 -12
  56. schemathesis/specs/openapi/stateful/inference.py +13 -11
  57. schemathesis/specs/openapi/stateful/links.py +5 -20
  58. schemathesis/specs/openapi/types/__init__.py +3 -0
  59. schemathesis/specs/openapi/types/v3.py +68 -0
  60. schemathesis/specs/openapi/utils.py +1 -13
  61. schemathesis/transport/requests.py +3 -11
  62. schemathesis/transport/serialization.py +63 -27
  63. schemathesis/transport/wsgi.py +1 -8
  64. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/METADATA +2 -2
  65. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/RECORD +68 -53
  66. schemathesis/specs/openapi/parameters.py +0 -405
  67. schemathesis/specs/openapi/security.py +0 -162
  68. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/WHEEL +0 -0
  69. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/entry_points.txt +0 -0
  70. {schemathesis-4.1.4.dist-info → schemathesis-4.2.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,162 +0,0 @@
1
- """Processing of ``securityDefinitions`` or ``securitySchemes`` keywords."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass
6
- from typing import TYPE_CHECKING, Any, ClassVar, Generator
7
-
8
- from .parameters import OpenAPI20Parameter, OpenAPI30Parameter, OpenAPIParameter
9
-
10
- if TYPE_CHECKING:
11
- from jsonschema import RefResolver
12
-
13
- from schemathesis.schemas import APIOperation
14
-
15
-
16
- @dataclass
17
- class BaseSecurityProcessor:
18
- api_key_locations: ClassVar[tuple[str, ...]] = ("header", "query")
19
- http_security_name: ClassVar[str] = "basic"
20
- parameter_cls: ClassVar[type[OpenAPIParameter]] = OpenAPI20Parameter
21
-
22
- def process_definitions(self, schema: dict[str, Any], operation: APIOperation, resolver: RefResolver) -> None:
23
- """Add relevant security parameters to data generation."""
24
- __tracebackhide__ = True
25
- for definition in self._get_active_definitions(schema, operation, resolver):
26
- name = definition.get("name")
27
- location = definition.get("in")
28
- if name is not None and location is not None and operation.get_parameter(name, location) is not None:
29
- # Such parameter is already defined
30
- continue
31
- if definition["type"] == "apiKey":
32
- self.process_api_key_security_definition(definition, operation)
33
- self.process_http_security_definition(definition, operation)
34
-
35
- @staticmethod
36
- def _get_security_requirements(schema: dict[str, Any], operation: APIOperation) -> list[dict]:
37
- global_requirements = schema.get("security", [])
38
- local_requirements = operation.definition.raw.get("security", None)
39
- if local_requirements is not None:
40
- return local_requirements
41
- return global_requirements
42
-
43
- @staticmethod
44
- def get_security_requirements(schema: dict[str, Any], operation: APIOperation) -> list[str]:
45
- """Get applied security requirements for the given API operation."""
46
- # https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#operation-object
47
- # > This definition overrides any declared top-level security.
48
- # > To remove a top-level security declaration, an empty array can be used.
49
- requirements = BaseSecurityProcessor._get_security_requirements(schema, operation)
50
- return [key for requirement in requirements for key in requirement]
51
-
52
- @staticmethod
53
- def has_optional_auth(schema: dict[str, Any], operation: APIOperation) -> bool:
54
- requirements = BaseSecurityProcessor._get_security_requirements(schema, operation)
55
- return {} in requirements
56
-
57
- def _get_active_definitions(
58
- self, schema: dict[str, Any], operation: APIOperation, resolver: RefResolver
59
- ) -> Generator[dict[str, Any], None, None]:
60
- """Get only security definitions active for the given API operation."""
61
- definitions = self.get_security_definitions(schema, resolver)
62
- requirements = self.get_security_requirements(schema, operation)
63
- for name, definition in definitions.items():
64
- if name in requirements:
65
- yield definition
66
-
67
- def get_security_definitions(self, schema: dict[str, Any], resolver: RefResolver) -> dict[str, Any]:
68
- return schema.get("securityDefinitions", {})
69
-
70
- def get_security_definitions_as_parameters(
71
- self, schema: dict[str, Any], operation: APIOperation, resolver: RefResolver, location: str
72
- ) -> list[dict[str, Any]]:
73
- """Security definitions converted to OAS parameters.
74
-
75
- We need it to get proper serialization that will be applied on generated values. For this case it is only
76
- coercing to a string.
77
- """
78
- return [
79
- self._to_parameter(definition)
80
- for definition in self._get_active_definitions(schema, operation, resolver)
81
- if self._is_match(definition, location)
82
- ]
83
-
84
- def process_api_key_security_definition(self, definition: dict[str, Any], operation: APIOperation) -> None:
85
- parameter = self.parameter_cls(self._make_api_key_parameter(definition))
86
- operation.add_parameter(parameter)
87
-
88
- def process_http_security_definition(self, definition: dict[str, Any], operation: APIOperation) -> None:
89
- if definition["type"] == self.http_security_name:
90
- parameter = self.parameter_cls(self._make_http_auth_parameter(definition))
91
- operation.add_parameter(parameter)
92
-
93
- def _is_match(self, definition: dict[str, Any], location: str) -> bool:
94
- return (definition["type"] == "apiKey" and location in self.api_key_locations) or (
95
- definition["type"] == self.http_security_name and location == "header"
96
- )
97
-
98
- def _to_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
99
- func = {
100
- "apiKey": self._make_api_key_parameter,
101
- self.http_security_name: self._make_http_auth_parameter,
102
- }[definition["type"]]
103
- return func(definition)
104
-
105
- def _make_http_auth_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
106
- schema = make_auth_header_schema(definition)
107
- return make_auth_header(**schema)
108
-
109
- def _make_api_key_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
110
- return make_api_key_schema(definition, type="string")
111
-
112
-
113
- def make_auth_header_schema(definition: dict[str, Any]) -> dict[str, str]:
114
- schema = definition.get("scheme", "basic").lower()
115
- return {"type": "string", "format": f"_{schema}_auth"}
116
-
117
-
118
- def make_auth_header(**kwargs: Any) -> dict[str, Any]:
119
- return {"name": "Authorization", "in": "header", "required": True, **kwargs}
120
-
121
-
122
- def make_api_key_schema(definition: dict[str, Any], **kwargs: Any) -> dict[str, Any]:
123
- return {"name": definition["name"], "required": True, "in": definition["in"], **kwargs}
124
-
125
-
126
- SwaggerSecurityProcessor = BaseSecurityProcessor
127
-
128
-
129
- @dataclass
130
- class OpenAPISecurityProcessor(BaseSecurityProcessor):
131
- api_key_locations: ClassVar[tuple[str, ...]] = ("header", "cookie", "query")
132
- http_security_name: ClassVar[str] = "http"
133
- parameter_cls: ClassVar[type[OpenAPIParameter]] = OpenAPI30Parameter
134
-
135
- def get_security_definitions(self, schema: dict[str, Any], resolver: RefResolver) -> dict[str, Any]:
136
- """In Open API 3 security definitions are located in ``components`` and may have references inside."""
137
- components = schema.get("components", {})
138
- security_schemes = components.get("securitySchemes", {})
139
- # At this point, the resolution scope could differ from the root scope, that's why we need to restore it
140
- # as now we resolve root-level references
141
- if len(resolver._scopes_stack) > 1:
142
- scope = resolver.resolution_scope
143
- resolver.pop_scope()
144
- else:
145
- scope = None
146
- resolve = resolver.resolve
147
- try:
148
- if "$ref" in security_schemes:
149
- return resolve(security_schemes["$ref"])[1]
150
- return {
151
- key: resolve(value["$ref"])[1] if "$ref" in value else value for key, value in security_schemes.items()
152
- }
153
- finally:
154
- if scope is not None:
155
- resolver._scopes_stack.append(scope)
156
-
157
- def _make_http_auth_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
158
- schema = make_auth_header_schema(definition)
159
- return make_auth_header(schema=schema)
160
-
161
- def _make_api_key_parameter(self, definition: dict[str, Any]) -> dict[str, Any]:
162
- return make_api_key_schema(definition, schema={"type": "string"})