robotframework-openapitools 0.4.0__py3-none-any.whl → 1.0.0b1__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.
- OpenApiDriver/__init__.py +44 -41
- OpenApiDriver/openapi_executors.py +40 -39
- OpenApiDriver/openapi_reader.py +115 -116
- OpenApiDriver/openapidriver.libspec +71 -61
- OpenApiDriver/openapidriver.py +25 -19
- OpenApiLibCore/__init__.py +13 -11
- OpenApiLibCore/annotations.py +3 -0
- OpenApiLibCore/data_generation/__init__.py +12 -0
- OpenApiLibCore/data_generation/body_data_generation.py +269 -0
- OpenApiLibCore/data_generation/data_generation_core.py +240 -0
- OpenApiLibCore/data_invalidation.py +281 -0
- OpenApiLibCore/dto_base.py +29 -35
- OpenApiLibCore/dto_utils.py +97 -85
- OpenApiLibCore/oas_cache.py +14 -13
- OpenApiLibCore/openapi_libcore.libspec +350 -193
- OpenApiLibCore/openapi_libcore.py +392 -1698
- OpenApiLibCore/parameter_utils.py +89 -0
- OpenApiLibCore/path_functions.py +215 -0
- OpenApiLibCore/path_invalidation.py +44 -0
- OpenApiLibCore/protocols.py +30 -0
- OpenApiLibCore/request_data.py +275 -0
- OpenApiLibCore/resource_relations.py +54 -0
- OpenApiLibCore/validation.py +497 -0
- OpenApiLibCore/value_utils.py +528 -481
- openapi_libgen/__init__.py +46 -0
- openapi_libgen/command_line.py +87 -0
- openapi_libgen/parsing_utils.py +26 -0
- openapi_libgen/spec_parser.py +212 -0
- openapi_libgen/templates/__init__.jinja +3 -0
- openapi_libgen/templates/library.jinja +30 -0
- robotframework_openapitools-1.0.0b1.dist-info/METADATA +237 -0
- robotframework_openapitools-1.0.0b1.dist-info/RECORD +37 -0
- {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0b1.dist-info}/WHEEL +1 -1
- robotframework_openapitools-1.0.0b1.dist-info/entry_points.txt +3 -0
- roboswag/__init__.py +0 -9
- roboswag/__main__.py +0 -3
- roboswag/auth.py +0 -44
- roboswag/cli.py +0 -80
- roboswag/core.py +0 -85
- roboswag/generate/__init__.py +0 -1
- roboswag/generate/generate.py +0 -121
- roboswag/generate/models/__init__.py +0 -0
- roboswag/generate/models/api.py +0 -219
- roboswag/generate/models/definition.py +0 -28
- roboswag/generate/models/endpoint.py +0 -68
- roboswag/generate/models/parameter.py +0 -25
- roboswag/generate/models/response.py +0 -8
- roboswag/generate/models/tag.py +0 -16
- roboswag/generate/models/utils.py +0 -60
- roboswag/generate/templates/api_init.jinja +0 -15
- roboswag/generate/templates/models.jinja +0 -7
- roboswag/generate/templates/paths.jinja +0 -68
- roboswag/logger.py +0 -33
- roboswag/validate/__init__.py +0 -6
- roboswag/validate/core.py +0 -3
- roboswag/validate/schema.py +0 -21
- roboswag/validate/text_response.py +0 -14
- robotframework_openapitools-0.4.0.dist-info/METADATA +0 -42
- robotframework_openapitools-0.4.0.dist-info/RECORD +0 -41
- {robotframework_openapitools-0.4.0.dist-info → robotframework_openapitools-1.0.0b1.dist-info}/LICENSE +0 -0
@@ -0,0 +1,269 @@
|
|
1
|
+
"""
|
2
|
+
Module holding the functions related to (json) data generation
|
3
|
+
for the body of requests made as part of keyword exection.
|
4
|
+
"""
|
5
|
+
|
6
|
+
from random import choice, randint, sample
|
7
|
+
from typing import Any
|
8
|
+
|
9
|
+
from robot.api import logger
|
10
|
+
|
11
|
+
import OpenApiLibCore.path_functions as pf
|
12
|
+
from OpenApiLibCore.annotations import JSON
|
13
|
+
from OpenApiLibCore.dto_base import (
|
14
|
+
Dto,
|
15
|
+
IdDependency,
|
16
|
+
PropertyValueConstraint,
|
17
|
+
)
|
18
|
+
from OpenApiLibCore.dto_utils import DefaultDto
|
19
|
+
from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
|
20
|
+
from OpenApiLibCore.protocols import GetIdPropertyNameType
|
21
|
+
from OpenApiLibCore.value_utils import IGNORE, get_valid_value
|
22
|
+
|
23
|
+
|
24
|
+
def get_json_data_for_dto_class(
|
25
|
+
schema: dict[str, Any],
|
26
|
+
dto_class: type[Dto],
|
27
|
+
get_id_property_name: GetIdPropertyNameType,
|
28
|
+
operation_id: str = "",
|
29
|
+
) -> JSON:
|
30
|
+
match schema.get("type"):
|
31
|
+
case "object":
|
32
|
+
return get_dict_data_for_dto_class(
|
33
|
+
schema=schema,
|
34
|
+
dto_class=dto_class,
|
35
|
+
get_id_property_name=get_id_property_name,
|
36
|
+
operation_id=operation_id,
|
37
|
+
)
|
38
|
+
case "array":
|
39
|
+
return get_list_data_for_dto_class(
|
40
|
+
schema=schema,
|
41
|
+
dto_class=dto_class,
|
42
|
+
get_id_property_name=get_id_property_name,
|
43
|
+
operation_id=operation_id,
|
44
|
+
)
|
45
|
+
case _:
|
46
|
+
return get_valid_value(value_schema=schema)
|
47
|
+
|
48
|
+
|
49
|
+
def get_dict_data_for_dto_class(
|
50
|
+
schema: dict[str, Any],
|
51
|
+
dto_class: type[Dto],
|
52
|
+
get_id_property_name: GetIdPropertyNameType,
|
53
|
+
operation_id: str = "",
|
54
|
+
) -> dict[str, Any]:
|
55
|
+
json_data: dict[str, Any] = {}
|
56
|
+
|
57
|
+
property_names = get_property_names_to_process(schema=schema, dto_class=dto_class)
|
58
|
+
|
59
|
+
for property_name in property_names:
|
60
|
+
property_schema = schema["properties"][property_name]
|
61
|
+
if property_schema.get("readOnly", False):
|
62
|
+
continue
|
63
|
+
|
64
|
+
json_data[property_name] = get_data_for_property(
|
65
|
+
property_name=property_name,
|
66
|
+
property_schema=property_schema,
|
67
|
+
get_id_property_name=get_id_property_name,
|
68
|
+
dto_class=dto_class,
|
69
|
+
operation_id=operation_id,
|
70
|
+
)
|
71
|
+
|
72
|
+
return json_data
|
73
|
+
|
74
|
+
|
75
|
+
def get_list_data_for_dto_class(
|
76
|
+
schema: dict[str, Any],
|
77
|
+
dto_class: type[Dto],
|
78
|
+
get_id_property_name: GetIdPropertyNameType,
|
79
|
+
operation_id: str = "",
|
80
|
+
) -> list[Any]:
|
81
|
+
json_data: list[Any] = []
|
82
|
+
list_item_schema = schema.get("items", {})
|
83
|
+
min_items = schema.get("minItems", 0)
|
84
|
+
max_items = schema.get("maxItems", 1)
|
85
|
+
number_of_items_to_generate = randint(min_items, max_items)
|
86
|
+
for _ in range(number_of_items_to_generate):
|
87
|
+
list_item_data = get_json_data_for_dto_class(
|
88
|
+
schema=list_item_schema,
|
89
|
+
dto_class=dto_class,
|
90
|
+
get_id_property_name=get_id_property_name,
|
91
|
+
operation_id=operation_id,
|
92
|
+
)
|
93
|
+
json_data.append(list_item_data)
|
94
|
+
return json_data
|
95
|
+
|
96
|
+
|
97
|
+
def get_data_for_property(
|
98
|
+
property_name: str,
|
99
|
+
property_schema: dict[str, Any],
|
100
|
+
get_id_property_name: GetIdPropertyNameType,
|
101
|
+
dto_class: type[Dto],
|
102
|
+
operation_id: str,
|
103
|
+
) -> JSON:
|
104
|
+
property_type = property_schema.get("type")
|
105
|
+
if property_type is None:
|
106
|
+
property_types = property_schema.get("types")
|
107
|
+
if property_types is None:
|
108
|
+
if property_schema.get("properties") is None:
|
109
|
+
raise NotImplementedError
|
110
|
+
|
111
|
+
nested_data = get_json_data_for_dto_class(
|
112
|
+
schema=property_schema,
|
113
|
+
dto_class=DefaultDto,
|
114
|
+
get_id_property_name=get_id_property_name,
|
115
|
+
)
|
116
|
+
return nested_data
|
117
|
+
|
118
|
+
selected_type_schema = choice(property_types)
|
119
|
+
property_type = selected_type_schema["type"]
|
120
|
+
property_schema = selected_type_schema
|
121
|
+
|
122
|
+
if constrained_values := get_constrained_values(
|
123
|
+
dto_class=dto_class, property_name=property_name
|
124
|
+
):
|
125
|
+
constrained_value = choice(constrained_values)
|
126
|
+
# Check if the chosen value is a nested Dto; since a Dto is never
|
127
|
+
# instantiated, we can use isinstance(..., type) for this.
|
128
|
+
if isinstance(constrained_value, type):
|
129
|
+
return get_value_constrained_by_nested_dto(
|
130
|
+
property_schema=property_schema,
|
131
|
+
nested_dto_class=constrained_value,
|
132
|
+
get_id_property_name=get_id_property_name,
|
133
|
+
operation_id=operation_id,
|
134
|
+
)
|
135
|
+
return constrained_value
|
136
|
+
|
137
|
+
if (
|
138
|
+
dependent_id := get_dependent_id(
|
139
|
+
dto_class=dto_class,
|
140
|
+
property_name=property_name,
|
141
|
+
operation_id=operation_id,
|
142
|
+
get_id_property_name=get_id_property_name,
|
143
|
+
)
|
144
|
+
) is not None:
|
145
|
+
return dependent_id
|
146
|
+
|
147
|
+
if property_type == "object":
|
148
|
+
object_data = get_json_data_for_dto_class(
|
149
|
+
schema=property_schema,
|
150
|
+
dto_class=DefaultDto,
|
151
|
+
get_id_property_name=get_id_property_name,
|
152
|
+
operation_id="",
|
153
|
+
)
|
154
|
+
return object_data
|
155
|
+
|
156
|
+
if property_type == "array":
|
157
|
+
array_data = get_json_data_for_dto_class(
|
158
|
+
schema=property_schema["items"],
|
159
|
+
dto_class=DefaultDto,
|
160
|
+
get_id_property_name=get_id_property_name,
|
161
|
+
operation_id=operation_id,
|
162
|
+
)
|
163
|
+
return [array_data]
|
164
|
+
|
165
|
+
return get_valid_value(property_schema)
|
166
|
+
|
167
|
+
|
168
|
+
def get_value_constrained_by_nested_dto(
|
169
|
+
property_schema: dict[str, Any],
|
170
|
+
nested_dto_class: type[Dto],
|
171
|
+
get_id_property_name: GetIdPropertyNameType,
|
172
|
+
operation_id: str,
|
173
|
+
) -> JSON:
|
174
|
+
nested_schema = get_schema_for_nested_dto(property_schema=property_schema)
|
175
|
+
nested_value = get_json_data_for_dto_class(
|
176
|
+
schema=nested_schema,
|
177
|
+
dto_class=nested_dto_class,
|
178
|
+
get_id_property_name=get_id_property_name,
|
179
|
+
operation_id=operation_id,
|
180
|
+
)
|
181
|
+
return nested_value
|
182
|
+
|
183
|
+
|
184
|
+
def get_schema_for_nested_dto(property_schema: dict[str, Any]) -> dict[str, Any]:
|
185
|
+
if property_schema.get("type"):
|
186
|
+
return property_schema
|
187
|
+
|
188
|
+
if possible_types := property_schema.get("types"):
|
189
|
+
return choice(possible_types)
|
190
|
+
|
191
|
+
raise NotImplementedError
|
192
|
+
|
193
|
+
|
194
|
+
def get_property_names_to_process(
|
195
|
+
schema: dict[str, Any],
|
196
|
+
dto_class: type[Dto],
|
197
|
+
) -> list[str]:
|
198
|
+
property_names = []
|
199
|
+
|
200
|
+
for property_name in schema.get("properties", []):
|
201
|
+
# register the oas_name
|
202
|
+
_ = get_safe_name_for_oas_name(property_name)
|
203
|
+
if constrained_values := get_constrained_values(
|
204
|
+
dto_class=dto_class, property_name=property_name
|
205
|
+
):
|
206
|
+
# do not add properties that are configured to be ignored
|
207
|
+
if IGNORE in constrained_values: # type: ignore[comparison-overlap]
|
208
|
+
continue
|
209
|
+
property_names.append(property_name)
|
210
|
+
|
211
|
+
max_properties = schema.get("maxProperties")
|
212
|
+
if max_properties and len(property_names) > max_properties:
|
213
|
+
required_properties = schema.get("required", [])
|
214
|
+
number_of_optional_properties = max_properties - len(required_properties)
|
215
|
+
optional_properties = [
|
216
|
+
name for name in property_names if name not in required_properties
|
217
|
+
]
|
218
|
+
selected_optional_properties = sample(
|
219
|
+
optional_properties, number_of_optional_properties
|
220
|
+
)
|
221
|
+
property_names = required_properties + selected_optional_properties
|
222
|
+
|
223
|
+
return property_names
|
224
|
+
|
225
|
+
|
226
|
+
def get_constrained_values(
|
227
|
+
dto_class: type[Dto], property_name: str
|
228
|
+
) -> list[JSON | type[Dto]]:
|
229
|
+
relations = dto_class.get_relations()
|
230
|
+
values_list = [
|
231
|
+
c.values
|
232
|
+
for c in relations
|
233
|
+
if (isinstance(c, PropertyValueConstraint) and c.property_name == property_name)
|
234
|
+
]
|
235
|
+
# values should be empty or contain 1 list of allowed values
|
236
|
+
return values_list.pop() if values_list else []
|
237
|
+
|
238
|
+
|
239
|
+
def get_dependent_id(
|
240
|
+
dto_class: type[Dto],
|
241
|
+
property_name: str,
|
242
|
+
operation_id: str,
|
243
|
+
get_id_property_name: GetIdPropertyNameType,
|
244
|
+
) -> str | int | float | None:
|
245
|
+
relations = dto_class.get_relations()
|
246
|
+
# multiple get paths are possible based on the operation being performed
|
247
|
+
id_get_paths = [
|
248
|
+
(d.get_path, d.operation_id)
|
249
|
+
for d in relations
|
250
|
+
if (isinstance(d, IdDependency) and d.property_name == property_name)
|
251
|
+
]
|
252
|
+
if not id_get_paths:
|
253
|
+
return None
|
254
|
+
if len(id_get_paths) == 1:
|
255
|
+
id_get_path, _ = id_get_paths.pop()
|
256
|
+
else:
|
257
|
+
try:
|
258
|
+
[id_get_path] = [
|
259
|
+
path for path, operation in id_get_paths if operation == operation_id
|
260
|
+
]
|
261
|
+
# There could be multiple get_paths, but not one for the current operation
|
262
|
+
except ValueError:
|
263
|
+
return None
|
264
|
+
|
265
|
+
valid_id = pf.get_valid_id_for_path(
|
266
|
+
path=id_get_path, get_id_property_name=get_id_property_name
|
267
|
+
)
|
268
|
+
logger.debug(f"get_dependent_id for {id_get_path} returned {valid_id}")
|
269
|
+
return valid_id
|
@@ -0,0 +1,240 @@
|
|
1
|
+
"""
|
2
|
+
Module holding the main functions related to data generation
|
3
|
+
for the requests made as part of keyword exection.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import re
|
7
|
+
from dataclasses import Field, field, make_dataclass
|
8
|
+
from random import choice
|
9
|
+
from typing import Any, cast
|
10
|
+
|
11
|
+
from robot.api import logger
|
12
|
+
|
13
|
+
import OpenApiLibCore.path_functions as pf
|
14
|
+
from OpenApiLibCore.annotations import JSON
|
15
|
+
from OpenApiLibCore.dto_base import (
|
16
|
+
Dto,
|
17
|
+
PropertyValueConstraint,
|
18
|
+
ResourceRelation,
|
19
|
+
resolve_schema,
|
20
|
+
)
|
21
|
+
from OpenApiLibCore.dto_utils import DefaultDto
|
22
|
+
from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
|
23
|
+
from OpenApiLibCore.protocols import GetDtoClassType, GetIdPropertyNameType
|
24
|
+
from OpenApiLibCore.request_data import RequestData
|
25
|
+
from OpenApiLibCore.value_utils import IGNORE, get_valid_value
|
26
|
+
|
27
|
+
from .body_data_generation import (
|
28
|
+
get_json_data_for_dto_class as _get_json_data_for_dto_class,
|
29
|
+
)
|
30
|
+
|
31
|
+
|
32
|
+
def get_request_data(
|
33
|
+
path: str,
|
34
|
+
method: str,
|
35
|
+
get_dto_class: GetDtoClassType,
|
36
|
+
get_id_property_name: GetIdPropertyNameType,
|
37
|
+
openapi_spec: dict[str, Any],
|
38
|
+
) -> RequestData:
|
39
|
+
method = method.lower()
|
40
|
+
dto_cls_name = get_dto_cls_name(path=path, method=method)
|
41
|
+
# The path can contain already resolved Ids that have to be matched
|
42
|
+
# against the parametrized paths in the paths section.
|
43
|
+
spec_path = pf.get_parametrized_path(path=path, openapi_spec=openapi_spec)
|
44
|
+
dto_class = get_dto_class(path=spec_path, method=method)
|
45
|
+
try:
|
46
|
+
method_spec = openapi_spec["paths"][spec_path][method]
|
47
|
+
except KeyError:
|
48
|
+
logger.info(
|
49
|
+
f"method '{method}' not supported on '{spec_path}, using empty spec."
|
50
|
+
)
|
51
|
+
method_spec = {}
|
52
|
+
|
53
|
+
parameters, params, headers = get_request_parameters(
|
54
|
+
dto_class=dto_class, method_spec=method_spec
|
55
|
+
)
|
56
|
+
if (body_spec := method_spec.get("requestBody", None)) is None:
|
57
|
+
dto_instance = _get_dto_instance_for_empty_body(
|
58
|
+
dto_class=dto_class,
|
59
|
+
dto_cls_name=dto_cls_name,
|
60
|
+
method_spec=method_spec,
|
61
|
+
)
|
62
|
+
return RequestData(
|
63
|
+
dto=dto_instance,
|
64
|
+
parameters=parameters,
|
65
|
+
params=params,
|
66
|
+
headers=headers,
|
67
|
+
has_body=False,
|
68
|
+
)
|
69
|
+
|
70
|
+
headers.update({"content-type": get_content_type(body_spec)})
|
71
|
+
|
72
|
+
content_schema = resolve_schema(get_content_schema(body_spec))
|
73
|
+
dto_data = _get_json_data_for_dto_class(
|
74
|
+
schema=content_schema,
|
75
|
+
dto_class=dto_class,
|
76
|
+
get_id_property_name=get_id_property_name,
|
77
|
+
operation_id=method_spec.get("operationId", ""),
|
78
|
+
)
|
79
|
+
dto_instance = _get_dto_instance_from_dto_data(
|
80
|
+
content_schema=content_schema,
|
81
|
+
dto_class=dto_class,
|
82
|
+
dto_data=dto_data,
|
83
|
+
method_spec=method_spec,
|
84
|
+
dto_cls_name=dto_cls_name,
|
85
|
+
)
|
86
|
+
return RequestData(
|
87
|
+
dto=dto_instance,
|
88
|
+
dto_schema=content_schema,
|
89
|
+
parameters=parameters,
|
90
|
+
params=params,
|
91
|
+
headers=headers,
|
92
|
+
)
|
93
|
+
|
94
|
+
|
95
|
+
def _get_dto_instance_for_empty_body(
|
96
|
+
dto_class: type[Dto],
|
97
|
+
dto_cls_name: str,
|
98
|
+
method_spec: dict[str, Any],
|
99
|
+
) -> Dto:
|
100
|
+
if dto_class == DefaultDto:
|
101
|
+
dto_instance: Dto = DefaultDto()
|
102
|
+
else:
|
103
|
+
dto_class = make_dataclass(
|
104
|
+
cls_name=method_spec.get("operationId", dto_cls_name),
|
105
|
+
fields=[],
|
106
|
+
bases=(dto_class,),
|
107
|
+
)
|
108
|
+
dto_instance = dto_class()
|
109
|
+
return dto_instance
|
110
|
+
|
111
|
+
|
112
|
+
def _get_dto_instance_from_dto_data(
|
113
|
+
content_schema: dict[str, Any],
|
114
|
+
dto_class: type[Dto],
|
115
|
+
dto_data: JSON,
|
116
|
+
method_spec: dict[str, Any],
|
117
|
+
dto_cls_name: str,
|
118
|
+
) -> Dto:
|
119
|
+
if not isinstance(dto_data, (dict, list)):
|
120
|
+
return DefaultDto()
|
121
|
+
|
122
|
+
if isinstance(dto_data, list):
|
123
|
+
raise NotImplementedError
|
124
|
+
|
125
|
+
fields = get_fields_from_dto_data(content_schema, dto_data)
|
126
|
+
dto_class_ = make_dataclass(
|
127
|
+
cls_name=method_spec.get("operationId", dto_cls_name),
|
128
|
+
fields=fields,
|
129
|
+
bases=(dto_class,),
|
130
|
+
)
|
131
|
+
dto_data = {get_safe_key(key): value for key, value in dto_data.items()}
|
132
|
+
return cast(Dto, dto_class_(**dto_data))
|
133
|
+
|
134
|
+
|
135
|
+
def get_fields_from_dto_data(
|
136
|
+
content_schema: dict[str, Any], dto_data: dict[str, JSON]
|
137
|
+
) -> list[tuple[str, type[Any], Field[Any]]]:
|
138
|
+
"""Get a dataclasses fields list based on the content_schema and dto_data."""
|
139
|
+
fields: list[tuple[str, type[Any], Field[Any]]] = []
|
140
|
+
for key, value in dto_data.items():
|
141
|
+
required_properties = content_schema.get("required", [])
|
142
|
+
safe_key = get_safe_key(key)
|
143
|
+
metadata = {"original_property_name": key}
|
144
|
+
if key in required_properties:
|
145
|
+
# The fields list is used to create a dataclass, so non-default fields
|
146
|
+
# must go before fields with a default
|
147
|
+
field_ = cast(Field[Any], field(metadata=metadata)) # pylint: disable=invalid-field-call
|
148
|
+
fields.insert(0, (safe_key, type(value), field_))
|
149
|
+
else:
|
150
|
+
field_ = cast(Field[Any], field(default=None, metadata=metadata)) # pylint: disable=invalid-field-call
|
151
|
+
fields.append((safe_key, type(value), field_))
|
152
|
+
return fields
|
153
|
+
|
154
|
+
|
155
|
+
def get_safe_key(key: str) -> str:
|
156
|
+
"""
|
157
|
+
Helper function to convert a valid JSON property name to a string that can be used
|
158
|
+
as a Python variable or function / method name.
|
159
|
+
"""
|
160
|
+
key = key.replace("-", "_")
|
161
|
+
key = key.replace("@", "_")
|
162
|
+
if key[0].isdigit():
|
163
|
+
key = f"_{key}"
|
164
|
+
return key
|
165
|
+
|
166
|
+
|
167
|
+
def get_dto_cls_name(path: str, method: str) -> str:
|
168
|
+
method = method.capitalize()
|
169
|
+
path = path.translate({ord(i): None for i in "{}"})
|
170
|
+
path_parts = path.split("/")
|
171
|
+
path_parts = [p.capitalize() for p in path_parts]
|
172
|
+
result = "".join([method, *path_parts])
|
173
|
+
return result
|
174
|
+
|
175
|
+
|
176
|
+
def get_content_schema(body_spec: dict[str, Any]) -> dict[str, Any]:
|
177
|
+
"""Get the content schema from the requestBody spec."""
|
178
|
+
content_type = get_content_type(body_spec)
|
179
|
+
content_schema = body_spec["content"][content_type]["schema"]
|
180
|
+
return resolve_schema(content_schema)
|
181
|
+
|
182
|
+
|
183
|
+
def get_content_type(body_spec: dict[str, Any]) -> str:
|
184
|
+
"""Get and validate the first supported content type from the requested body spec
|
185
|
+
|
186
|
+
Should be application/json like content type,
|
187
|
+
e.g "application/json;charset=utf-8" or "application/merge-patch+json"
|
188
|
+
"""
|
189
|
+
content_types: list[str] = body_spec["content"].keys()
|
190
|
+
json_regex = r"application/([a-z\-]+\+)?json(;\s?charset=(.+))?"
|
191
|
+
for content_type in content_types:
|
192
|
+
if re.search(json_regex, content_type):
|
193
|
+
return content_type
|
194
|
+
|
195
|
+
# At present no supported for other types.
|
196
|
+
raise NotImplementedError(
|
197
|
+
f"Only content types like 'application/json' are supported. "
|
198
|
+
f"Content types definded in the spec are '{content_types}'."
|
199
|
+
)
|
200
|
+
|
201
|
+
|
202
|
+
def get_request_parameters(
|
203
|
+
dto_class: Dto | type[Dto], method_spec: dict[str, Any]
|
204
|
+
) -> tuple[list[dict[str, Any]], dict[str, Any], dict[str, str]]:
|
205
|
+
"""Get the methods parameter spec and params and headers with valid data."""
|
206
|
+
parameters = method_spec.get("parameters", [])
|
207
|
+
parameter_relations = dto_class.get_parameter_relations()
|
208
|
+
query_params = [p for p in parameters if p.get("in") == "query"]
|
209
|
+
header_params = [p for p in parameters if p.get("in") == "header"]
|
210
|
+
params = get_parameter_data(query_params, parameter_relations)
|
211
|
+
headers = get_parameter_data(header_params, parameter_relations)
|
212
|
+
return parameters, params, headers
|
213
|
+
|
214
|
+
|
215
|
+
def get_parameter_data(
|
216
|
+
parameters: list[dict[str, Any]],
|
217
|
+
parameter_relations: list[ResourceRelation],
|
218
|
+
) -> dict[str, str]:
|
219
|
+
"""Generate a valid list of key-value pairs for all parameters."""
|
220
|
+
result: dict[str, str] = {}
|
221
|
+
value: Any = None
|
222
|
+
for parameter in parameters:
|
223
|
+
parameter_name = parameter["name"]
|
224
|
+
# register the oas_name
|
225
|
+
_ = get_safe_name_for_oas_name(parameter_name)
|
226
|
+
parameter_schema = resolve_schema(parameter["schema"])
|
227
|
+
relations = [
|
228
|
+
r for r in parameter_relations if r.property_name == parameter_name
|
229
|
+
]
|
230
|
+
if constrained_values := [
|
231
|
+
r.values for r in relations if isinstance(r, PropertyValueConstraint)
|
232
|
+
]:
|
233
|
+
value = choice(*constrained_values)
|
234
|
+
if value is IGNORE:
|
235
|
+
continue
|
236
|
+
result[parameter_name] = value
|
237
|
+
continue
|
238
|
+
value = get_valid_value(parameter_schema)
|
239
|
+
result[parameter_name] = value
|
240
|
+
return result
|