robotframework-openapitools 1.0.0b3__py3-none-any.whl → 1.0.0b5__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 (34) hide show
  1. OpenApiDriver/openapi_executors.py +15 -11
  2. OpenApiDriver/openapi_reader.py +12 -13
  3. OpenApiDriver/openapidriver.libspec +5 -42
  4. OpenApiLibCore/__init__.py +0 -2
  5. OpenApiLibCore/annotations.py +8 -1
  6. OpenApiLibCore/data_generation/__init__.py +0 -2
  7. OpenApiLibCore/data_generation/body_data_generation.py +54 -73
  8. OpenApiLibCore/data_generation/data_generation_core.py +75 -82
  9. OpenApiLibCore/data_invalidation.py +38 -25
  10. OpenApiLibCore/dto_base.py +48 -105
  11. OpenApiLibCore/dto_utils.py +31 -3
  12. OpenApiLibCore/localized_faker.py +88 -0
  13. OpenApiLibCore/models.py +723 -0
  14. OpenApiLibCore/openapi_libcore.libspec +48 -284
  15. OpenApiLibCore/openapi_libcore.py +54 -71
  16. OpenApiLibCore/parameter_utils.py +20 -14
  17. OpenApiLibCore/path_functions.py +10 -10
  18. OpenApiLibCore/path_invalidation.py +5 -7
  19. OpenApiLibCore/protocols.py +13 -5
  20. OpenApiLibCore/request_data.py +67 -102
  21. OpenApiLibCore/resource_relations.py +6 -5
  22. OpenApiLibCore/validation.py +50 -167
  23. OpenApiLibCore/value_utils.py +46 -358
  24. openapi_libgen/__init__.py +0 -46
  25. openapi_libgen/command_line.py +7 -19
  26. openapi_libgen/generator.py +84 -0
  27. openapi_libgen/parsing_utils.py +9 -5
  28. openapi_libgen/spec_parser.py +41 -114
  29. {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/METADATA +2 -1
  30. robotframework_openapitools-1.0.0b5.dist-info/RECORD +40 -0
  31. robotframework_openapitools-1.0.0b3.dist-info/RECORD +0 -37
  32. {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/LICENSE +0 -0
  33. {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/WHEEL +0 -0
  34. {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/entry_points.txt +0 -0
@@ -3,26 +3,31 @@ Module holding the main functions related to data generation
3
3
  for the requests made as part of keyword exection.
4
4
  """
5
5
 
6
- import re
7
6
  from dataclasses import Field, field, make_dataclass
8
7
  from random import choice
9
8
  from typing import Any, cast
10
9
 
11
10
  from robot.api import logger
12
11
 
13
- import OpenApiLibCore.path_functions as pf
12
+ import OpenApiLibCore.path_functions as _path_functions
14
13
  from OpenApiLibCore.annotations import JSON
15
14
  from OpenApiLibCore.dto_base import (
16
15
  Dto,
17
16
  PropertyValueConstraint,
18
17
  ResourceRelation,
19
- resolve_schema,
20
18
  )
21
19
  from OpenApiLibCore.dto_utils import DefaultDto
20
+ from OpenApiLibCore.models import (
21
+ ObjectSchema,
22
+ OpenApiObject,
23
+ OperationObject,
24
+ ParameterObject,
25
+ UnionTypeSchema,
26
+ )
22
27
  from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
23
28
  from OpenApiLibCore.protocols import GetDtoClassType, GetIdPropertyNameType
24
29
  from OpenApiLibCore.request_data import RequestData
25
- from OpenApiLibCore.value_utils import IGNORE, get_valid_value
30
+ from OpenApiLibCore.value_utils import IGNORE
26
31
 
27
32
  from .body_data_generation import (
28
33
  get_json_data_for_dto_class as _get_json_data_for_dto_class,
@@ -34,30 +39,35 @@ def get_request_data(
34
39
  method: str,
35
40
  get_dto_class: GetDtoClassType,
36
41
  get_id_property_name: GetIdPropertyNameType,
37
- openapi_spec: dict[str, Any],
42
+ openapi_spec: OpenApiObject,
38
43
  ) -> RequestData:
39
44
  method = method.lower()
40
45
  dto_cls_name = get_dto_cls_name(path=path, method=method)
41
46
  # The path can contain already resolved Ids that have to be matched
42
47
  # against the parametrized paths in the paths section.
43
- spec_path = pf.get_parametrized_path(path=path, openapi_spec=openapi_spec)
48
+ spec_path = _path_functions.get_parametrized_path(
49
+ path=path, openapi_spec=openapi_spec
50
+ )
44
51
  dto_class = get_dto_class(path=spec_path, method=method)
45
52
  try:
46
- method_spec = openapi_spec["paths"][spec_path][method]
47
- except KeyError:
53
+ path_item = openapi_spec.paths[spec_path]
54
+ operation_spec: OperationObject | None = getattr(path_item, method)
55
+ if operation_spec is None:
56
+ raise AttributeError
57
+ except AttributeError:
48
58
  logger.info(
49
59
  f"method '{method}' not supported on '{spec_path}, using empty spec."
50
60
  )
51
- method_spec = {}
61
+ operation_spec = OperationObject(operationId="")
52
62
 
53
63
  parameters, params, headers = get_request_parameters(
54
- dto_class=dto_class, method_spec=method_spec
64
+ dto_class=dto_class, method_spec=operation_spec
55
65
  )
56
- if (body_spec := method_spec.get("requestBody", None)) is None:
66
+ if operation_spec.requestBody is None:
57
67
  dto_instance = _get_dto_instance_for_empty_body(
58
68
  dto_class=dto_class,
59
69
  dto_cls_name=dto_cls_name,
60
- method_spec=method_spec,
70
+ method_spec=operation_spec,
61
71
  )
62
72
  return RequestData(
63
73
  dto=dto_instance,
@@ -67,25 +77,38 @@ def get_request_data(
67
77
  has_body=False,
68
78
  )
69
79
 
70
- headers.update({"content-type": get_content_type(body_spec)})
80
+ body_schema = operation_spec.requestBody.schema_
81
+
82
+ if not body_schema:
83
+ raise ValueError(
84
+ f"No supported content schema found: {operation_spec.requestBody.content}"
85
+ )
86
+
87
+ headers.update({"content-type": operation_spec.requestBody.mime_type})
88
+
89
+ if isinstance(body_schema, UnionTypeSchema):
90
+ resolved_schemas = body_schema.resolved_schemas
91
+ body_schema = choice(resolved_schemas)
92
+
93
+ if not isinstance(body_schema, ObjectSchema):
94
+ raise ValueError(f"Selected schema is not an object schema: {body_schema}")
71
95
 
72
- content_schema = resolve_schema(get_content_schema(body_spec))
73
96
  dto_data = _get_json_data_for_dto_class(
74
- schema=content_schema,
97
+ schema=body_schema,
75
98
  dto_class=dto_class,
76
99
  get_id_property_name=get_id_property_name,
77
- operation_id=method_spec.get("operationId", ""),
100
+ operation_id=operation_spec.operationId,
78
101
  )
79
102
  dto_instance = _get_dto_instance_from_dto_data(
80
- content_schema=content_schema,
103
+ object_schema=body_schema,
81
104
  dto_class=dto_class,
82
105
  dto_data=dto_data,
83
- method_spec=method_spec,
106
+ method_spec=operation_spec,
84
107
  dto_cls_name=dto_cls_name,
85
108
  )
86
109
  return RequestData(
87
110
  dto=dto_instance,
88
- dto_schema=content_schema,
111
+ body_schema=body_schema,
89
112
  parameters=parameters,
90
113
  params=params,
91
114
  headers=headers,
@@ -95,13 +118,14 @@ def get_request_data(
95
118
  def _get_dto_instance_for_empty_body(
96
119
  dto_class: type[Dto],
97
120
  dto_cls_name: str,
98
- method_spec: dict[str, Any],
121
+ method_spec: OperationObject,
99
122
  ) -> Dto:
100
123
  if dto_class == DefaultDto:
101
124
  dto_instance: Dto = DefaultDto()
102
125
  else:
126
+ cls_name = method_spec.operationId if method_spec.operationId else dto_cls_name
103
127
  dto_class = make_dataclass(
104
- cls_name=method_spec.get("operationId", dto_cls_name),
128
+ cls_name=cls_name,
105
129
  fields=[],
106
130
  bases=(dto_class,),
107
131
  )
@@ -110,10 +134,10 @@ def _get_dto_instance_for_empty_body(
110
134
 
111
135
 
112
136
  def _get_dto_instance_from_dto_data(
113
- content_schema: dict[str, Any],
137
+ object_schema: ObjectSchema,
114
138
  dto_class: type[Dto],
115
139
  dto_data: JSON,
116
- method_spec: dict[str, Any],
140
+ method_spec: OperationObject,
117
141
  dto_cls_name: str,
118
142
  ) -> Dto:
119
143
  if not isinstance(dto_data, (dict, list)):
@@ -122,48 +146,41 @@ def _get_dto_instance_from_dto_data(
122
146
  if isinstance(dto_data, list):
123
147
  raise NotImplementedError
124
148
 
125
- fields = get_fields_from_dto_data(content_schema, dto_data)
149
+ fields = get_fields_from_dto_data(object_schema, dto_data)
150
+ cls_name = method_spec.operationId if method_spec.operationId else dto_cls_name
126
151
  dto_class_ = make_dataclass(
127
- cls_name=method_spec.get("operationId", dto_cls_name),
152
+ cls_name=cls_name,
128
153
  fields=fields,
129
154
  bases=(dto_class,),
130
155
  )
131
- dto_data = {get_safe_key(key): value for key, value in dto_data.items()}
156
+ # dto_data = {get_safe_key(key): value for key, value in dto_data.items()}
157
+ dto_data = {
158
+ get_safe_name_for_oas_name(key): value for key, value in dto_data.items()
159
+ }
132
160
  return cast(Dto, dto_class_(**dto_data))
133
161
 
134
162
 
135
163
  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]]]:
164
+ object_schema: ObjectSchema, dto_data: dict[str, JSON]
165
+ ) -> list[tuple[str, type[object], Field[object]]]:
138
166
  """Get a dataclasses fields list based on the content_schema and dto_data."""
139
- fields: list[tuple[str, type[Any], Field[Any]]] = []
167
+ fields: list[tuple[str, type[object], Field[object]]] = []
168
+
140
169
  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:
170
+ # safe_key = get_safe_key(key)
171
+ safe_key = get_safe_name_for_oas_name(key)
172
+ # metadata = {"original_property_name": key}
173
+ if key in object_schema.required:
145
174
  # The fields list is used to create a dataclass, so non-default fields
146
175
  # must go before fields with a default
147
- field_ = cast(Field[Any], field(metadata=metadata)) # pylint: disable=invalid-field-call
176
+ field_ = cast(Field[Any], field()) # pylint: disable=invalid-field-call
148
177
  fields.insert(0, (safe_key, type(value), field_))
149
178
  else:
150
- field_ = cast(Field[Any], field(default=None, metadata=metadata)) # pylint: disable=invalid-field-call
179
+ field_ = cast(Field[Any], field(default=None)) # pylint: disable=invalid-field-call
151
180
  fields.append((safe_key, type(value), field_))
152
181
  return fields
153
182
 
154
183
 
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
184
  def get_dto_cls_name(path: str, method: str) -> str:
168
185
  method = method.capitalize()
169
186
  path = path.translate({ord(i): None for i in "{}"})
@@ -173,57 +190,30 @@ def get_dto_cls_name(path: str, method: str) -> str:
173
190
  return result
174
191
 
175
192
 
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
193
  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]]:
194
+ dto_class: Dto | type[Dto], method_spec: OperationObject
195
+ ) -> tuple[list[ParameterObject], dict[str, Any], dict[str, str]]:
205
196
  """Get the methods parameter spec and params and headers with valid data."""
206
- parameters = method_spec.get("parameters", [])
197
+ parameters = method_spec.parameters if method_spec.parameters else []
207
198
  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"]
199
+ query_params = [p for p in parameters if p.in_ == "query"]
200
+ header_params = [p for p in parameters if p.in_ == "header"]
210
201
  params = get_parameter_data(query_params, parameter_relations)
211
202
  headers = get_parameter_data(header_params, parameter_relations)
212
203
  return parameters, params, headers
213
204
 
214
205
 
215
206
  def get_parameter_data(
216
- parameters: list[dict[str, Any]],
207
+ parameters: list[ParameterObject],
217
208
  parameter_relations: list[ResourceRelation],
218
209
  ) -> dict[str, str]:
219
210
  """Generate a valid list of key-value pairs for all parameters."""
220
211
  result: dict[str, str] = {}
221
212
  value: Any = None
222
213
  for parameter in parameters:
223
- parameter_name = parameter["name"]
214
+ parameter_name = parameter.name
224
215
  # register the oas_name
225
216
  _ = get_safe_name_for_oas_name(parameter_name)
226
- parameter_schema = resolve_schema(parameter["schema"])
227
217
  relations = [
228
218
  r for r in parameter_relations if r.property_name == parameter_name
229
219
  ]
@@ -235,6 +225,9 @@ def get_parameter_data(
235
225
  continue
236
226
  result[parameter_name] = value
237
227
  continue
238
- value = get_valid_value(parameter_schema)
228
+
229
+ if parameter.schema_ is None:
230
+ continue
231
+ value = parameter.schema_.get_valid_value()
239
232
  result[parameter_name] = value
240
233
  return result
@@ -11,22 +11,22 @@ from requests import Response
11
11
  from robot.api import logger
12
12
  from robot.libraries.BuiltIn import BuiltIn
13
13
 
14
+ from OpenApiLibCore.annotations import JSON
14
15
  from OpenApiLibCore.dto_base import (
15
16
  NOT_SET,
16
17
  Dto,
17
18
  IdReference,
18
- PathPropertiesConstraint,
19
19
  PropertyValueConstraint,
20
20
  UniquePropertyValueConstraint,
21
- resolve_schema,
22
21
  )
22
+ from OpenApiLibCore.models import ParameterObject, UnionTypeSchema
23
23
  from OpenApiLibCore.request_data import RequestData
24
- from OpenApiLibCore.value_utils import IGNORE, get_invalid_value, get_valid_value
24
+ from OpenApiLibCore.value_utils import IGNORE, get_invalid_value
25
25
 
26
26
  run_keyword = BuiltIn().run_keyword
27
27
 
28
28
 
29
- def get_invalid_json_data(
29
+ def get_invalid_body_data(
30
30
  url: str,
31
31
  method: str,
32
32
  status_code: int,
@@ -34,17 +34,14 @@ def get_invalid_json_data(
34
34
  invalid_property_default_response: int,
35
35
  ) -> dict[str, Any]:
36
36
  method = method.lower()
37
- data_relations = request_data.dto.get_relations_for_error_code(status_code)
38
- data_relations = [
39
- r for r in data_relations if not isinstance(r, PathPropertiesConstraint)
40
- ]
37
+ data_relations = request_data.dto.get_body_relations_for_error_code(status_code)
41
38
  if not data_relations:
42
- if not request_data.dto_schema:
39
+ if request_data.body_schema is None:
43
40
  raise ValueError(
44
- "Failed to invalidate: no data_relations and empty schema."
41
+ "Failed to invalidate: request_data does not contain a body_schema."
45
42
  )
46
43
  json_data = request_data.dto.get_invalidated_data(
47
- schema=request_data.dto_schema,
44
+ schema=request_data.body_schema,
48
45
  status_code=status_code,
49
46
  invalid_property_default_code=invalid_property_default_response,
50
47
  )
@@ -62,8 +59,12 @@ def get_invalid_json_data(
62
59
  run_keyword("ensure_in_use", url, resource_relation)
63
60
  json_data = request_data.dto.as_dict()
64
61
  else:
62
+ if request_data.body_schema is None:
63
+ raise ValueError(
64
+ "Failed to invalidate: request_data does not contain a body_schema."
65
+ )
65
66
  json_data = request_data.dto.get_invalidated_data(
66
- schema=request_data.dto_schema,
67
+ schema=request_data.body_schema,
67
68
  status_code=status_code,
68
69
  invalid_property_default_code=invalid_property_default_response,
69
70
  )
@@ -72,7 +73,7 @@ def get_invalid_json_data(
72
73
 
73
74
  def get_invalidated_parameters(
74
75
  status_code: int, request_data: RequestData, invalid_property_default_response: int
75
- ) -> tuple[dict[str, Any], dict[str, str]]:
76
+ ) -> tuple[dict[str, JSON], dict[str, JSON]]:
76
77
  if not request_data.parameters:
77
78
  raise ValueError("No params or headers to invalidate.")
78
79
 
@@ -115,7 +116,7 @@ def get_invalidated_parameters(
115
116
 
116
117
  # Dto mappings may contain generic mappings for properties that are not present
117
118
  # in this specific schema
118
- request_data_parameter_names = [p.get("name") for p in request_data.parameters]
119
+ request_data_parameter_names = [p.name for p in request_data.parameters]
119
120
  additional_relation_property_names = {
120
121
  n for n in relation_property_names if n not in request_data_parameter_names
121
122
  }
@@ -140,7 +141,7 @@ def get_invalidated_parameters(
140
141
  [parameter_data] = [
141
142
  data
142
143
  for data in request_data.parameters
143
- if data["name"] == parameter_to_invalidate
144
+ if data.name == parameter_to_invalidate
144
145
  ]
145
146
  except Exception:
146
147
  raise ValueError(
@@ -186,7 +187,14 @@ def get_invalidated_parameters(
186
187
  else:
187
188
  valid_value = headers[parameter_to_invalidate]
188
189
 
189
- value_schema = resolve_schema(parameter_data["schema"])
190
+ value_schema = parameter_data.schema_
191
+ if value_schema is None:
192
+ raise ValueError(f"No schema defined for parameter: {parameter_data}.")
193
+
194
+ if isinstance(value_schema, UnionTypeSchema):
195
+ # FIXME: extra handling may be needed in case of values_from_constraint
196
+ value_schema = choice(value_schema.resolved_schemas)
197
+
190
198
  invalid_value = get_invalid_value(
191
199
  value_schema=value_schema,
192
200
  current_value=valid_value,
@@ -204,11 +212,11 @@ def get_invalidated_parameters(
204
212
 
205
213
  def ensure_parameter_in_parameters(
206
214
  parameter_to_invalidate: str,
207
- params: dict[str, Any],
208
- headers: dict[str, str],
209
- parameter_data: dict[str, Any],
210
- values_from_constraint: list[Any],
211
- ) -> tuple[dict[str, Any], dict[str, str]]:
215
+ params: dict[str, JSON],
216
+ headers: dict[str, JSON],
217
+ parameter_data: ParameterObject,
218
+ values_from_constraint: list[JSON],
219
+ ) -> tuple[dict[str, JSON], dict[str, JSON]]:
212
220
  """
213
221
  Returns the params, headers tuple with parameter_to_invalidate with a valid
214
222
  value to params or headers if not originally present.
@@ -220,15 +228,20 @@ def ensure_parameter_in_parameters(
220
228
  if values_from_constraint:
221
229
  valid_value = choice(values_from_constraint)
222
230
  else:
223
- parameter_schema = resolve_schema(parameter_data["schema"])
224
- valid_value = get_valid_value(parameter_schema)
231
+ value_schema = parameter_data.schema_
232
+ if value_schema is None:
233
+ raise ValueError(f"No schema defined for parameter: {parameter_data}.")
234
+
235
+ if isinstance(value_schema, UnionTypeSchema):
236
+ value_schema = choice(value_schema.resolved_schemas)
237
+ valid_value = value_schema.get_valid_value()
225
238
  if (
226
- parameter_data["in"] == "query"
239
+ parameter_data.in_ == "query"
227
240
  and parameter_to_invalidate not in params.keys()
228
241
  ):
229
242
  params[parameter_to_invalidate] = valid_value
230
243
  if (
231
- parameter_data["in"] == "header"
244
+ parameter_data.in_ == "header"
232
245
  and parameter_to_invalidate not in headers.keys()
233
246
  ):
234
247
  headers[parameter_to_invalidate] = str(valid_value)