robotframework-openapitools 1.0.0b2__py3-none-any.whl → 1.0.0b4__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 (31) hide show
  1. OpenApiDriver/openapi_executors.py +8 -8
  2. OpenApiDriver/openapi_reader.py +12 -13
  3. OpenApiDriver/openapidriver.libspec +4 -41
  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 +52 -71
  8. OpenApiLibCore/data_generation/data_generation_core.py +82 -62
  9. OpenApiLibCore/data_invalidation.py +37 -20
  10. OpenApiLibCore/dto_base.py +20 -86
  11. OpenApiLibCore/localized_faker.py +88 -0
  12. OpenApiLibCore/models.py +715 -0
  13. OpenApiLibCore/openapi_libcore.libspec +47 -283
  14. OpenApiLibCore/openapi_libcore.py +20 -46
  15. OpenApiLibCore/parameter_utils.py +23 -17
  16. OpenApiLibCore/path_functions.py +5 -4
  17. OpenApiLibCore/protocols.py +7 -5
  18. OpenApiLibCore/request_data.py +67 -102
  19. OpenApiLibCore/resource_relations.py +2 -3
  20. OpenApiLibCore/validation.py +49 -161
  21. OpenApiLibCore/value_utils.py +46 -358
  22. openapi_libgen/__init__.py +0 -46
  23. openapi_libgen/command_line.py +7 -19
  24. openapi_libgen/generator.py +84 -0
  25. openapi_libgen/spec_parser.py +44 -111
  26. {robotframework_openapitools-1.0.0b2.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/METADATA +98 -1
  27. robotframework_openapitools-1.0.0b4.dist-info/RECORD +40 -0
  28. robotframework_openapitools-1.0.0b2.dist-info/RECORD +0 -37
  29. {robotframework_openapitools-1.0.0b2.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/LICENSE +0 -0
  30. {robotframework_openapitools-1.0.0b2.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/WHEEL +0 -0
  31. {robotframework_openapitools-1.0.0b2.dist-info → robotframework_openapitools-1.0.0b4.dist-info}/entry_points.txt +0 -0
@@ -152,6 +152,11 @@ from OpenApiLibCore.dto_utils import (
152
152
  get_dto_class,
153
153
  get_id_property_name,
154
154
  )
155
+ from OpenApiLibCore.localized_faker import FAKE
156
+ from OpenApiLibCore.models import (
157
+ OpenApiObject,
158
+ PathItemObject,
159
+ )
155
160
  from OpenApiLibCore.oas_cache import PARSER_CACHE, CachedParser
156
161
  from OpenApiLibCore.parameter_utils import (
157
162
  get_oas_name_from_safe_name,
@@ -159,11 +164,10 @@ from OpenApiLibCore.parameter_utils import (
159
164
  )
160
165
  from OpenApiLibCore.protocols import ResponseValidatorType
161
166
  from OpenApiLibCore.request_data import RequestData, RequestValues
162
- from OpenApiLibCore.value_utils import FAKE
163
167
 
164
168
  run_keyword = BuiltIn().run_keyword
165
169
  default_str_mapping: Mapping[str, str] = MappingProxyType({})
166
- default_any_mapping: Mapping[str, object] = MappingProxyType({})
170
+ default_json_mapping: Mapping[str, JSON] = MappingProxyType({})
167
171
 
168
172
 
169
173
  @library(scope="SUITE", doc_format="ROBOT")
@@ -434,7 +438,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
434
438
  self,
435
439
  path: str,
436
440
  method: str,
437
- overrides: Mapping[str, object] = default_any_mapping,
441
+ overrides: Mapping[str, JSON] = default_json_mapping,
438
442
  ) -> RequestValues:
439
443
  """Return an object with all (valid) request values needed to make a request."""
440
444
  json_data: dict[str, JSON] = {}
@@ -482,24 +486,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
482
486
  )
483
487
 
484
488
  @keyword
485
- def get_json_data_for_dto_class(
486
- self,
487
- schema: dict[str, JSON],
488
- dto_class: type[Dto],
489
- operation_id: str = "",
490
- ) -> JSON:
491
- """
492
- Generate valid (json-compatible) data for the `dto_class`.
493
- """
494
- return _data_generation.get_json_data_for_dto_class(
495
- schema=schema,
496
- dto_class=dto_class,
497
- get_id_property_name=self.get_id_property_name,
498
- operation_id=operation_id,
499
- )
500
-
501
- @keyword
502
- def get_invalid_json_data(
489
+ def get_invalid_body_data(
503
490
  self,
504
491
  url: str,
505
492
  method: str,
@@ -513,7 +500,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
513
500
  > Note: applicable UniquePropertyValueConstraint and IdReference Relations are
514
501
  considered before changes to `json_data` are made.
515
502
  """
516
- return di.get_invalid_json_data(
503
+ return di.get_invalid_body_data(
517
504
  url=url,
518
505
  method=method,
519
506
  status_code=status_code,
@@ -526,7 +513,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
526
513
  self,
527
514
  status_code: int,
528
515
  request_data: RequestData,
529
- ) -> tuple[dict[str, JSON], dict[str, str]]:
516
+ ) -> tuple[dict[str, JSON], dict[str, JSON]]:
530
517
  """
531
518
  Returns a version of `params, headers` as present on `request_data` that has
532
519
  been modified to cause the provided `status_code`.
@@ -706,7 +693,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
706
693
  path: str,
707
694
  status_code: int,
708
695
  request_values: RequestValues,
709
- original_data: Mapping[str, object] = default_any_mapping,
696
+ original_data: Mapping[str, JSON] = default_json_mapping,
710
697
  ) -> None:
711
698
  """
712
699
  This keyword first calls the Authorized Request keyword, then the Validate
@@ -751,7 +738,7 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
751
738
  self,
752
739
  path: str,
753
740
  response: Response,
754
- original_data: Mapping[str, object] = default_any_mapping,
741
+ original_data: Mapping[str, JSON] = default_json_mapping,
755
742
  ) -> None:
756
743
  """
757
744
  Validate the `response` by performing the following validations:
@@ -775,24 +762,11 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
775
762
  original_data=original_data,
776
763
  )
777
764
 
778
- @keyword
779
- def validate_resource_properties(
780
- self, resource: dict[str, JSON], schema: dict[str, JSON]
781
- ) -> None:
782
- """
783
- Validate that the `resource` does not contain any properties that are not
784
- defined in the `schema_properties`.
785
- """
786
- val.validate_resource_properties(
787
- resource=resource,
788
- schema=schema,
789
- )
790
-
791
765
  @staticmethod
792
766
  @keyword
793
767
  def validate_send_response(
794
768
  response: Response,
795
- original_data: Mapping[str, object] = default_any_mapping,
769
+ original_data: Mapping[str, JSON] = default_json_mapping,
796
770
  ) -> None:
797
771
  """
798
772
  Validate that each property that was send that is in the response has the value
@@ -818,17 +792,17 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
818
792
  return validation_spec
819
793
 
820
794
  @property
821
- def openapi_spec(self) -> dict[str, JSON]:
795
+ def openapi_spec(self) -> OpenApiObject:
822
796
  """Return a deepcopy of the parsed openapi document."""
823
797
  # protect the parsed openapi spec from being mutated by reference
824
798
  return deepcopy(self._openapi_spec)
825
799
 
826
800
  @cached_property
827
- def _openapi_spec(self) -> dict[str, JSON]:
801
+ def _openapi_spec(self) -> OpenApiObject:
828
802
  parser, _, _ = self._load_specs_and_validator()
829
- spec_dict: dict[str, JSON] = parser.specification
830
- register_path_parameters(spec_dict["paths"])
831
- return spec_dict
803
+ spec_model = OpenApiObject.model_validate(parser.specification)
804
+ register_path_parameters(spec_model.paths)
805
+ return spec_model
832
806
 
833
807
  @cached_property
834
808
  def response_validator(
@@ -927,5 +901,5 @@ class OpenApiLibCore: # pylint: disable=too-many-public-methods
927
901
  f"ValidationError while trying to load openapi spec: {exception}"
928
902
  ) from exception
929
903
 
930
- def read_paths(self) -> dict[str, JSON]:
931
- return self.openapi_spec["paths"] # type: ignore[return-value]
904
+ def read_paths(self) -> dict[str, PathItemObject]:
905
+ return self.openapi_spec.paths
@@ -5,7 +5,7 @@ names and the original names in the parsed OpenApi Specification document.
5
5
 
6
6
  from typing import Generator
7
7
 
8
- from OpenApiLibCore.annotations import JSON
8
+ from OpenApiLibCore.models import ParameterObject, PathItemObject
9
9
 
10
10
  PARAMETER_REGISTRY: dict[str, str] = {
11
11
  "body": "body",
@@ -20,11 +20,11 @@ def get_oas_name_from_safe_name(safe_name: str) -> str:
20
20
 
21
21
 
22
22
  def get_safe_name_for_oas_name(oas_name: str) -> str:
23
- if is_python_safe(oas_name):
23
+ if _is_python_safe(oas_name):
24
24
  PARAMETER_REGISTRY[oas_name] = oas_name
25
25
  return oas_name
26
26
 
27
- safe_name = convert_string_to_python_identifier(oas_name)
27
+ safe_name = _convert_string_to_python_identifier(oas_name)
28
28
 
29
29
  if safe_name not in PARAMETER_REGISTRY:
30
30
  PARAMETER_REGISTRY[safe_name] = oas_name
@@ -36,17 +36,17 @@ def get_safe_name_for_oas_name(oas_name: str) -> str:
36
36
  # We're dealing with multiple oas_names that convert to the same safe_name.
37
37
  # To resolve this, a more verbose safe_name is generated. This is less user-friendly
38
38
  # but necessary to ensure an one-to-one mapping.
39
- verbose_safe_name = convert_string_to_python_identifier(oas_name, verbose=True)
39
+ verbose_safe_name = _convert_string_to_python_identifier(oas_name, verbose=True)
40
40
  if verbose_safe_name not in PARAMETER_REGISTRY:
41
41
  PARAMETER_REGISTRY[verbose_safe_name] = oas_name
42
42
  return verbose_safe_name
43
43
 
44
44
 
45
- def is_python_safe(name: str) -> bool:
45
+ def _is_python_safe(name: str) -> bool:
46
46
  return name.isidentifier()
47
47
 
48
48
 
49
- def convert_string_to_python_identifier(string: str, verbose: bool = False) -> str:
49
+ def _convert_string_to_python_identifier(string: str, verbose: bool = False) -> str:
50
50
  def _convert_string_to_python_identifier() -> Generator[str, None, None]:
51
51
  string_iterator = iter(string)
52
52
 
@@ -71,21 +71,27 @@ def convert_string_to_python_identifier(string: str, verbose: bool = False) -> s
71
71
  ascii_code = ord(character)
72
72
  yield f"_{ascii_code}_"
73
73
 
74
- if is_python_safe(string):
74
+ if _is_python_safe(string):
75
75
  return string
76
76
 
77
77
  converted_string = "".join(_convert_string_to_python_identifier())
78
- if not is_python_safe(converted_string):
78
+ if not _is_python_safe(converted_string):
79
79
  raise ValueError(f"Failed to convert '{string}' to Python identifier.")
80
80
  return converted_string
81
81
 
82
82
 
83
- def register_path_parameters(paths_data: dict[str, dict[str, JSON]]) -> None:
84
- for operations_data in paths_data.values():
85
- for method_data in operations_data.values():
86
- parameters_data: list[dict[str, str]] = method_data.get("parameters", [])
87
- path_parameter_names = [
88
- p["name"] for p in parameters_data if p["in"] == "path"
89
- ]
90
- for name in path_parameter_names:
91
- _ = get_safe_name_for_oas_name(name)
83
+ def register_path_parameters(paths_data: dict[str, PathItemObject]) -> None:
84
+ def _register_path_parameter(parameter_object: ParameterObject) -> None:
85
+ if parameter_object.in_ == "path":
86
+ _ = get_safe_name_for_oas_name(parameter_object.name)
87
+
88
+ for path_item in paths_data.values():
89
+ if parameters := path_item.parameters:
90
+ for parameter in path_item.parameters:
91
+ _register_path_parameter(parameter_object=parameter)
92
+
93
+ operations = path_item.get_operations()
94
+ for operation in operations.values():
95
+ if parameters := operation.parameters:
96
+ for parameter in parameters:
97
+ _register_path_parameter(parameter_object=parameter)
@@ -9,6 +9,7 @@ from requests import Response
9
9
  from robot.libraries.BuiltIn import BuiltIn
10
10
 
11
11
  from OpenApiLibCore.dto_base import PathPropertiesConstraint
12
+ from OpenApiLibCore.models import OpenApiObject
12
13
  from OpenApiLibCore.protocols import GetDtoClassType, GetIdPropertyNameType
13
14
  from OpenApiLibCore.request_data import RequestData
14
15
 
@@ -24,14 +25,14 @@ def match_parts(parts: list[str], spec_parts: list[str]) -> bool:
24
25
  return True
25
26
 
26
27
 
27
- def get_parametrized_path(path: str, openapi_spec: dict[str, Any]) -> str:
28
+ def get_parametrized_path(path: str, openapi_spec: OpenApiObject) -> str:
28
29
  path_parts = path.split("/")
29
30
  # if the last part is empty, the path has a trailing `/` that
30
31
  # should be ignored during matching
31
32
  if path_parts[-1] == "":
32
33
  _ = path_parts.pop(-1)
33
34
 
34
- spec_paths: list[str] = {**openapi_spec}["paths"].keys()
35
+ spec_paths: list[str] = list(openapi_spec.paths.keys())
35
36
 
36
37
  candidates: list[str] = []
37
38
 
@@ -64,12 +65,12 @@ def get_valid_url(
64
65
  path: str,
65
66
  base_url: str,
66
67
  get_dto_class: GetDtoClassType,
67
- openapi_spec: dict[str, Any],
68
+ openapi_spec: OpenApiObject,
68
69
  ) -> str:
69
70
  try:
70
71
  # path can be partially resolved or provided by a PathPropertiesConstraint
71
72
  parametrized_path = get_parametrized_path(path=path, openapi_spec=openapi_spec)
72
- _ = openapi_spec["paths"][parametrized_path]
73
+ _ = openapi_spec.paths[parametrized_path]
73
74
  except KeyError:
74
75
  raise ValueError(
75
76
  f"{path} not found in paths section of the OpenAPI document."
@@ -13,18 +13,20 @@ from OpenApiLibCore.dto_base import Dto
13
13
  class ResponseValidatorType(Protocol):
14
14
  def __call__(
15
15
  self, request: RequestsOpenAPIRequest, response: RequestsOpenAPIResponse
16
- ) -> None: ...
16
+ ) -> None: ... # pragma: no cover
17
17
 
18
18
 
19
19
  class GetDtoClassType(Protocol):
20
- def __init__(self, mappings_module_name: str) -> None: ...
20
+ def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
21
21
 
22
- def __call__(self, path: str, method: str) -> Type[Dto]: ...
22
+ def __call__(self, path: str, method: str) -> Type[Dto]: ... # pragma: no cover
23
23
 
24
24
 
25
25
  class GetIdPropertyNameType(Protocol):
26
- def __init__(self, mappings_module_name: str) -> None: ...
26
+ def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
27
27
 
28
28
  def __call__(
29
29
  self, path: str
30
- ) -> tuple[str, Callable[[str], str] | Callable[[int], int]]: ...
30
+ ) -> tuple[
31
+ str, Callable[[str], str] | Callable[[int], int]
32
+ ]: ... # pragma: no cover
@@ -6,11 +6,15 @@ from functools import cached_property
6
6
  from random import sample
7
7
  from typing import Any
8
8
 
9
- from OpenApiLibCore.dto_base import (
10
- Dto,
11
- resolve_schema,
12
- )
9
+ from OpenApiLibCore.annotations import JSON
10
+ from OpenApiLibCore.dto_base import Dto
13
11
  from OpenApiLibCore.dto_utils import DefaultDto
12
+ from OpenApiLibCore.models import (
13
+ ObjectSchema,
14
+ ParameterObject,
15
+ ResolvedSchemaObjectTypes,
16
+ UnionTypeSchema,
17
+ )
14
18
 
15
19
 
16
20
  @dataclass
@@ -19,23 +23,23 @@ class RequestValues:
19
23
 
20
24
  url: str
21
25
  method: str
22
- params: dict[str, Any] = field(default_factory=dict)
23
- headers: dict[str, str] = field(default_factory=dict)
24
- json_data: dict[str, Any] = field(default_factory=dict)
26
+ params: dict[str, JSON] = field(default_factory=dict)
27
+ headers: dict[str, JSON] = field(default_factory=dict)
28
+ json_data: dict[str, JSON] = field(default_factory=dict)
25
29
 
26
- def override_body_value(self, name: str, value: Any) -> None:
30
+ def override_body_value(self, name: str, value: JSON) -> None:
27
31
  if name in self.json_data:
28
32
  self.json_data[name] = value
29
33
 
30
- def override_header_value(self, name: str, value: Any) -> None:
34
+ def override_header_value(self, name: str, value: JSON) -> None:
31
35
  if name in self.headers:
32
36
  self.headers[name] = value
33
37
 
34
- def override_param_value(self, name: str, value: str) -> None:
38
+ def override_param_value(self, name: str, value: JSON) -> None:
35
39
  if name in self.params:
36
40
  self.params[name] = str(value)
37
41
 
38
- def override_request_value(self, name: str, value: Any) -> None:
42
+ def override_request_value(self, name: str, value: JSON) -> None:
39
43
  self.override_body_value(name=name, value=value)
40
44
  self.override_header_value(name=name, value=value)
41
45
  self.override_param_value(name=name, value=value)
@@ -52,16 +56,14 @@ class RequestData:
52
56
  """Helper class to manage parameters used when making requests."""
53
57
 
54
58
  dto: Dto | DefaultDto = field(default_factory=DefaultDto)
55
- dto_schema: dict[str, Any] = field(default_factory=dict)
56
- parameters: list[dict[str, Any]] = field(default_factory=list)
57
- params: dict[str, Any] = field(default_factory=dict)
58
- headers: dict[str, Any] = field(default_factory=dict)
59
+ body_schema: ObjectSchema | None = None
60
+ parameters: list[ParameterObject] = field(default_factory=list)
61
+ params: dict[str, JSON] = field(default_factory=dict)
62
+ headers: dict[str, JSON] = field(default_factory=dict)
59
63
  has_body: bool = True
60
64
 
61
65
  def __post_init__(self) -> None:
62
66
  # prevent modification by reference
63
- self.dto_schema = deepcopy(self.dto_schema)
64
- self.parameters = deepcopy(self.parameters)
65
67
  self.params = deepcopy(self.params)
66
68
  self.headers = deepcopy(self.headers)
67
69
 
@@ -70,20 +72,24 @@ class RequestData:
70
72
  """Whether or not the dto data (json data) contains optional properties."""
71
73
 
72
74
  def is_required_property(property_name: str) -> bool:
73
- return property_name in self.dto_schema.get("required", [])
75
+ return property_name in self.required_property_names
74
76
 
75
77
  properties = (self.dto.as_dict()).keys()
76
78
  return not all(map(is_required_property, properties))
77
79
 
80
+ @property
81
+ def required_property_names(self) -> list[str]:
82
+ if self.body_schema:
83
+ return self.body_schema.required
84
+ return []
85
+
78
86
  @property
79
87
  def has_optional_params(self) -> bool:
80
88
  """Whether or not any of the query parameters are optional."""
81
89
 
82
90
  def is_optional_param(query_param: str) -> bool:
83
91
  optional_params = [
84
- p.get("name")
85
- for p in self.parameters
86
- if p.get("in") == "query" and not p.get("required")
92
+ p.name for p in self.parameters if p.in_ == "query" and not p.required
87
93
  ]
88
94
  return query_param in optional_params
89
95
 
@@ -96,47 +102,26 @@ class RequestData:
96
102
  restrictions, data type or by not providing them in a request.
97
103
  """
98
104
  result = set()
99
- params = [h for h in self.parameters if h.get("in") == "query"]
105
+ params = [h for h in self.parameters if h.in_ == "query"]
100
106
  for param in params:
101
107
  # required params can be omitted to invalidate a request
102
- if param["required"]:
103
- result.add(param["name"])
108
+ if param.required:
109
+ result.add(param.name)
104
110
  continue
105
111
 
106
- schema = resolve_schema(param["schema"])
107
- if schema.get("type", None):
108
- param_types = [schema]
112
+ if param.schema_ is None:
113
+ continue
114
+
115
+ possible_schemas: list[ResolvedSchemaObjectTypes] = []
116
+ if isinstance(param.schema_, UnionTypeSchema):
117
+ possible_schemas = param.schema_.resolved_schemas
109
118
  else:
110
- param_types = schema["types"]
111
- for param_type in param_types:
112
- # any basic non-string type except "null" can be invalidated by
113
- # replacing it with a string
114
- if param_type["type"] not in ["string", "array", "object", "null"]:
115
- result.add(param["name"])
116
- continue
117
- # enums, strings and arrays with boundaries can be invalidated
118
- if set(param_type.keys()).intersection(
119
- {
120
- "enum",
121
- "minLength",
122
- "maxLength",
123
- "minItems",
124
- "maxItems",
125
- }
126
- ):
127
- result.add(param["name"])
128
- continue
129
- # an array of basic non-string type can be invalidated by replacing the
130
- # items in the array with strings
131
- if param_type["type"] == "array" and param_type["items"][
132
- "type"
133
- ] not in [
134
- "string",
135
- "array",
136
- "object",
137
- "null",
138
- ]:
139
- result.add(param["name"])
119
+ possible_schemas = [param.schema_]
120
+
121
+ for param_schema in possible_schemas:
122
+ if param_schema.can_be_invalidated:
123
+ result.add(param.name)
124
+
140
125
  return result
141
126
 
142
127
  @property
@@ -145,9 +130,7 @@ class RequestData:
145
130
 
146
131
  def is_optional_header(header: str) -> bool:
147
132
  optional_headers = [
148
- p.get("name")
149
- for p in self.parameters
150
- if p.get("in") == "header" and not p.get("required")
133
+ p.name for p in self.parameters if p.in_ == "header" and not p.required
151
134
  ]
152
135
  return header in optional_headers
153
136
 
@@ -160,47 +143,26 @@ class RequestData:
160
143
  restrictions or by not providing them in a request.
161
144
  """
162
145
  result = set()
163
- headers = [h for h in self.parameters if h.get("in") == "header"]
146
+ headers = [h for h in self.parameters if h.in_ == "header"]
164
147
  for header in headers:
165
148
  # required headers can be omitted to invalidate a request
166
- if header["required"]:
167
- result.add(header["name"])
149
+ if header.required:
150
+ result.add(header.name)
151
+ continue
152
+
153
+ if header.schema_ is None:
168
154
  continue
169
155
 
170
- schema = resolve_schema(header["schema"])
171
- if schema.get("type", None):
172
- header_types = [schema]
156
+ possible_schemas: list[ResolvedSchemaObjectTypes] = []
157
+ if isinstance(header.schema_, UnionTypeSchema):
158
+ possible_schemas = header.schema_.resolved_schemas
173
159
  else:
174
- header_types = schema["types"]
175
- for header_type in header_types:
176
- # any basic non-string type except "null" can be invalidated by
177
- # replacing it with a string
178
- if header_type["type"] not in ["string", "array", "object", "null"]:
179
- result.add(header["name"])
180
- continue
181
- # enums, strings and arrays with boundaries can be invalidated
182
- if set(header_type.keys()).intersection(
183
- {
184
- "enum",
185
- "minLength",
186
- "maxLength",
187
- "minItems",
188
- "maxItems",
189
- }
190
- ):
191
- result.add(header["name"])
192
- continue
193
- # an array of basic non-string type can be invalidated by replacing the
194
- # items in the array with strings
195
- if header_type["type"] == "array" and header_type["items"][
196
- "type"
197
- ] not in [
198
- "string",
199
- "array",
200
- "object",
201
- "null",
202
- ]:
203
- result.add(header["name"])
160
+ possible_schemas = [header.schema_]
161
+
162
+ for param_schema in possible_schemas:
163
+ if param_schema.can_be_invalidated:
164
+ result.add(header.name)
165
+
204
166
  return result
205
167
 
206
168
  def get_required_properties_dict(self) -> dict[str, Any]:
@@ -211,7 +173,7 @@ class RequestData:
211
173
  for relation in relations
212
174
  if getattr(relation, "treat_as_mandatory", False)
213
175
  ]
214
- required_properties: list[str] = self.dto_schema.get("required", [])
176
+ required_properties = self.body_schema.required if self.body_schema else []
215
177
  required_properties.extend(mandatory_properties)
216
178
 
217
179
  required_properties_dict: dict[str, Any] = {}
@@ -223,7 +185,10 @@ class RequestData:
223
185
  def get_minimal_body_dict(self) -> dict[str, Any]:
224
186
  required_properties_dict = self.get_required_properties_dict()
225
187
 
226
- min_properties = self.dto_schema.get("minProperties", 0)
188
+ min_properties = 0
189
+ if self.body_schema and self.body_schema.minProperties is not None:
190
+ min_properties = self.body_schema.minProperties
191
+
227
192
  number_of_optional_properties_to_add = min_properties - len(
228
193
  required_properties_dict
229
194
  )
@@ -247,13 +212,13 @@ class RequestData:
247
212
 
248
213
  return {**required_properties_dict, **optional_properties_dict}
249
214
 
250
- def get_required_params(self) -> dict[str, str]:
215
+ def get_required_params(self) -> dict[str, JSON]:
251
216
  """Get the params dict containing only the required query parameters."""
252
217
  return {
253
218
  k: v for k, v in self.params.items() if k in self.required_parameter_names
254
219
  }
255
220
 
256
- def get_required_headers(self) -> dict[str, str]:
221
+ def get_required_headers(self) -> dict[str, JSON]:
257
222
  """Get the headers dict containing only the required headers."""
258
223
  return {
259
224
  k: v for k, v in self.headers.items() if k in self.required_parameter_names
@@ -271,11 +236,11 @@ class RequestData:
271
236
  for relation in relations
272
237
  if getattr(relation, "treat_as_mandatory", False)
273
238
  ]
274
- parameter_names = [p["name"] for p in self.parameters]
239
+ parameter_names = [p.name for p in self.parameters]
275
240
  mandatory_parameters = [
276
241
  p for p in mandatory_property_names if p in parameter_names
277
242
  ]
278
243
 
279
- required_parameters = [p["name"] for p in self.parameters if p.get("required")]
244
+ required_parameters = [p.name for p in self.parameters if p.required]
280
245
  required_parameters.extend(mandatory_parameters)
281
246
  return required_parameters
@@ -1,13 +1,12 @@
1
1
  """Module holding the functions related to relations between resources."""
2
2
 
3
- from typing import Any
4
-
5
3
  from requests import Response
6
4
  from robot.api import logger
7
5
  from robot.libraries.BuiltIn import BuiltIn
8
6
 
9
7
  import OpenApiLibCore.path_functions as pf
10
8
  from OpenApiLibCore.dto_base import IdReference
9
+ from OpenApiLibCore.models import OpenApiObject
11
10
  from OpenApiLibCore.request_data import RequestData
12
11
 
13
12
  run_keyword = BuiltIn().run_keyword
@@ -16,7 +15,7 @@ run_keyword = BuiltIn().run_keyword
16
15
  def ensure_in_use(
17
16
  url: str,
18
17
  base_url: str,
19
- openapi_spec: dict[str, Any],
18
+ openapi_spec: OpenApiObject,
20
19
  resource_relation: IdReference,
21
20
  ) -> None:
22
21
  resource_id = ""