robotframework-openapitools 1.0.0b3__tar.gz → 1.0.0b5__tar.gz
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.
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/PKG-INFO +2 -1
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/docs/README.md +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/pyproject.toml +4 -4
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapi_executors.py +15 -11
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapi_reader.py +12 -13
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapidriver.libspec +5 -42
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/__init__.py +0 -2
- robotframework_openapitools-1.0.0b5/src/OpenApiLibCore/annotations.py +10 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_generation/__init__.py +0 -2
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_generation/body_data_generation.py +54 -73
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_generation/data_generation_core.py +75 -82
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/data_invalidation.py +38 -25
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/dto_base.py +48 -105
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/dto_utils.py +31 -3
- robotframework_openapitools-1.0.0b5/src/OpenApiLibCore/localized_faker.py +88 -0
- robotframework_openapitools-1.0.0b5/src/OpenApiLibCore/models.py +723 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/openapi_libcore.libspec +48 -284
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/openapi_libcore.py +54 -71
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/parameter_utils.py +20 -14
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/path_functions.py +10 -10
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/path_invalidation.py +5 -7
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/protocols.py +13 -5
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/request_data.py +67 -102
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/resource_relations.py +6 -5
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/validation.py +50 -167
- robotframework_openapitools-1.0.0b5/src/OpenApiLibCore/value_utils.py +216 -0
- robotframework_openapitools-1.0.0b5/src/openapi_libgen/__init__.py +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/command_line.py +7 -19
- robotframework_openapitools-1.0.0b5/src/openapi_libgen/generator.py +84 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/parsing_utils.py +9 -5
- robotframework_openapitools-1.0.0b5/src/openapi_libgen/spec_parser.py +154 -0
- robotframework_openapitools-1.0.0b3/src/OpenApiLibCore/annotations.py +0 -3
- robotframework_openapitools-1.0.0b3/src/OpenApiLibCore/value_utils.py +0 -528
- robotframework_openapitools-1.0.0b3/src/openapi_libgen/__init__.py +0 -46
- robotframework_openapitools-1.0.0b3/src/openapi_libgen/spec_parser.py +0 -227
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/LICENSE +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/__init__.py +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/openapidriver.py +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiDriver/py.typed +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/oas_cache.py +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/OpenApiLibCore/py.typed +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/templates/__init__.jinja +0 -0
- {robotframework_openapitools-1.0.0b3 → robotframework_openapitools-1.0.0b5}/src/openapi_libgen/templates/library.jinja +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: robotframework-openapitools
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.0b5
|
4
4
|
Summary: A set of Robot Framework libraries to test APIs for which the OAS is available.
|
5
5
|
License: Apache License
|
6
6
|
Version 2.0, January 2004
|
@@ -220,6 +220,7 @@ Requires-Dist: Jinja2 (>=3.1.2)
|
|
220
220
|
Requires-Dist: black (>=24.1.0)
|
221
221
|
Requires-Dist: openapi-core (>=0.19.0)
|
222
222
|
Requires-Dist: prance[cli] (>=23)
|
223
|
+
Requires-Dist: pydantic (>=2.11.0)
|
223
224
|
Requires-Dist: requests (>=2.31.0)
|
224
225
|
Requires-Dist: rich_click (>=1.7.0)
|
225
226
|
Requires-Dist: robotframework (>=6.0.0,!=7.0.0)
|
File without changes
|
@@ -1,11 +1,9 @@
|
|
1
1
|
[project]
|
2
2
|
name="robotframework-openapitools"
|
3
|
-
version = "1.0.
|
3
|
+
version = "1.0.0b5"
|
4
4
|
description = "A set of Robot Framework libraries to test APIs for which the OAS is available."
|
5
5
|
authors = [
|
6
6
|
{name = "Robin Mackaij", email = "r.a.mackaij@gmail.com"},
|
7
|
-
{name = "Bartlomiej Hirsz", email = "bartek.hirsz@gmail.com"},
|
8
|
-
{name = "Mateusz Nojek", email = "matnojek@gmail.com"},
|
9
7
|
]
|
10
8
|
maintainers = [
|
11
9
|
{name = "Robin Mackaij", email = "r.a.mackaij@gmail.com"},
|
@@ -33,6 +31,7 @@ dependencies = [
|
|
33
31
|
"rich_click >= 1.7.0",
|
34
32
|
"black >= 24.1.0",
|
35
33
|
"Jinja2 >= 3.1.2",
|
34
|
+
"pydantic >= 2.11.0",
|
36
35
|
]
|
37
36
|
|
38
37
|
[dependency-groups]
|
@@ -101,7 +100,8 @@ build-backend = "poetry.core.masonry.api"
|
|
101
100
|
[tool.coverage.run]
|
102
101
|
branch = true
|
103
102
|
parallel = true
|
104
|
-
source = ["src/OpenApiDriver", "src/OpenApiLibCore"]
|
103
|
+
source = ["src/OpenApiDriver", "src/OpenApiLibCore", "src/openapi_libgen"]
|
104
|
+
omit = ["src/openapi_libgen/command_line.py"]
|
105
105
|
|
106
106
|
[tool.coverage.report]
|
107
107
|
exclude_lines = [
|
@@ -133,11 +133,15 @@ class OpenApiExecutors(OpenApiLibCore):
|
|
133
133
|
"""
|
134
134
|
valid_url: str = run_keyword("get_valid_url", path)
|
135
135
|
|
136
|
-
|
137
|
-
url
|
136
|
+
try:
|
137
|
+
url = run_keyword(
|
138
138
|
"get_invalidated_url", valid_url, path, expected_status_code
|
139
139
|
)
|
140
|
-
|
140
|
+
except Exception as exception:
|
141
|
+
message = getattr(exception, "message", "")
|
142
|
+
if not message.startswith("ValueError"):
|
143
|
+
raise exception # pragma: no cover
|
144
|
+
|
141
145
|
raise SkipExecution(
|
142
146
|
f"Path {path} does not contain resource references that "
|
143
147
|
f"can be invalidated."
|
@@ -185,8 +189,8 @@ class OpenApiExecutors(OpenApiLibCore):
|
|
185
189
|
# in case of a status code indicating an error, ensure the error occurs
|
186
190
|
if status_code >= int(HTTPStatus.BAD_REQUEST):
|
187
191
|
invalidation_keyword_data = {
|
188
|
-
"
|
189
|
-
"
|
192
|
+
"get_invalid_body_data": [
|
193
|
+
"get_invalid_body_data",
|
190
194
|
url,
|
191
195
|
method,
|
192
196
|
status_code,
|
@@ -201,13 +205,13 @@ class OpenApiExecutors(OpenApiLibCore):
|
|
201
205
|
invalidation_keywords = []
|
202
206
|
|
203
207
|
if request_data.dto.get_body_relations_for_error_code(status_code):
|
204
|
-
invalidation_keywords.append("
|
208
|
+
invalidation_keywords.append("get_invalid_body_data")
|
205
209
|
if request_data.dto.get_parameter_relations_for_error_code(status_code):
|
206
210
|
invalidation_keywords.append("get_invalidated_parameters")
|
207
211
|
if invalidation_keywords:
|
208
212
|
if (
|
209
213
|
invalidation_keyword := choice(invalidation_keywords)
|
210
|
-
) == "
|
214
|
+
) == "get_invalid_body_data":
|
211
215
|
json_data = run_keyword(
|
212
216
|
*invalidation_keyword_data[invalidation_keyword]
|
213
217
|
)
|
@@ -225,13 +229,13 @@ class OpenApiExecutors(OpenApiLibCore):
|
|
225
229
|
params, headers = run_keyword(
|
226
230
|
*invalidation_keyword_data["get_invalidated_parameters"]
|
227
231
|
)
|
228
|
-
if request_data.
|
232
|
+
if request_data.body_schema:
|
229
233
|
json_data = run_keyword(
|
230
|
-
*invalidation_keyword_data["
|
234
|
+
*invalidation_keyword_data["get_invalid_body_data"]
|
231
235
|
)
|
232
|
-
elif request_data.
|
236
|
+
elif request_data.body_schema:
|
233
237
|
json_data = run_keyword(
|
234
|
-
*invalidation_keyword_data["
|
238
|
+
*invalidation_keyword_data["get_invalid_body_data"]
|
235
239
|
)
|
236
240
|
else:
|
237
241
|
raise SkipExecution(
|
@@ -1,10 +1,12 @@
|
|
1
1
|
"""Module holding the OpenApiReader reader_class implementation."""
|
2
2
|
|
3
|
-
from typing import
|
3
|
+
from typing import Sequence
|
4
4
|
|
5
5
|
from DataDriver.AbstractReaderClass import AbstractReaderClass
|
6
6
|
from DataDriver.ReaderConfig import TestCaseData
|
7
7
|
|
8
|
+
from OpenApiLibCore.models import PathItemObject
|
9
|
+
|
8
10
|
|
9
11
|
class Test:
|
10
12
|
"""
|
@@ -16,7 +18,7 @@ class Test:
|
|
16
18
|
self.method = method.lower()
|
17
19
|
self.response = str(response)
|
18
20
|
|
19
|
-
def __eq__(self, other:
|
21
|
+
def __eq__(self, other: object) -> bool:
|
20
22
|
if not isinstance(other, type(self)):
|
21
23
|
return False
|
22
24
|
return (
|
@@ -33,7 +35,7 @@ class OpenApiReader(AbstractReaderClass):
|
|
33
35
|
test_data: list[TestCaseData] = []
|
34
36
|
|
35
37
|
read_paths_method = getattr(self, "read_paths_method")
|
36
|
-
paths: dict[str,
|
38
|
+
paths: dict[str, PathItemObject] = read_paths_method()
|
37
39
|
self._filter_paths(paths)
|
38
40
|
|
39
41
|
ignored_responses_ = [
|
@@ -43,15 +45,12 @@ class OpenApiReader(AbstractReaderClass):
|
|
43
45
|
ignored_tests = [Test(*test) for test in getattr(self, "ignored_testcases", [])]
|
44
46
|
|
45
47
|
for path, path_item in paths.items():
|
48
|
+
path_operations = path_item.get_operations()
|
49
|
+
|
46
50
|
# by reseversing the items, post/put operations come before get and delete
|
47
|
-
for
|
48
|
-
|
49
|
-
|
50
|
-
if item_name not in ["get", "put", "post", "delete", "patch"]:
|
51
|
-
continue
|
52
|
-
method, method_data = item_name, item_data
|
53
|
-
tags_from_spec = method_data.get("tags", [])
|
54
|
-
for response in method_data.get("responses"):
|
51
|
+
for method, operation_data in reversed(path_operations.items()):
|
52
|
+
tags_from_spec = operation_data.tags
|
53
|
+
for response in operation_data.responses.keys():
|
55
54
|
# 'default' applies to all status codes that are not specified, in
|
56
55
|
# which case we don't know what to expect and therefore can't verify
|
57
56
|
if (
|
@@ -76,7 +75,7 @@ class OpenApiReader(AbstractReaderClass):
|
|
76
75
|
)
|
77
76
|
return test_data
|
78
77
|
|
79
|
-
def _filter_paths(self, paths: dict[str,
|
78
|
+
def _filter_paths(self, paths: dict[str, PathItemObject]) -> None:
|
80
79
|
def matches_include_pattern(path: str) -> bool:
|
81
80
|
for included_path in included_paths:
|
82
81
|
if path == included_path:
|
@@ -111,5 +110,5 @@ class OpenApiReader(AbstractReaderClass):
|
|
111
110
|
paths.pop(path)
|
112
111
|
|
113
112
|
|
114
|
-
def _get_tag_list(tags:
|
113
|
+
def _get_tag_list(tags: Sequence[str], method: str, response: str) -> list[str]:
|
115
114
|
return [*tags, f"Method: {method.upper()}", f"Response: {response}"]
|
@@ -1,12 +1,12 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<keywordspec name="OpenApiDriver" type="LIBRARY" format="HTML" scope="SUITE" generated="2025-
|
3
|
-
<version>1.0.
|
2
|
+
<keywordspec name="OpenApiDriver" type="LIBRARY" format="HTML" scope="SUITE" generated="2025-06-09T18:38:35+00:00" specversion="6" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapidriver.py" lineno="358">
|
3
|
+
<version>1.0.0b5</version>
|
4
4
|
<doc><p>Visit the <a href="https://github.com/MarketSquare/robotframework-openapidriver">library page</a> for an introduction and examples.</p></doc>
|
5
5
|
<tags>
|
6
6
|
</tags>
|
7
7
|
<inits>
|
8
8
|
<init name="__init__" lineno="150">
|
9
|
-
<arguments repr="source: str, origin: str = , base_path: str = , included_paths: Iterable[str] = frozenset(), ignored_paths: Iterable[str] = frozenset(), ignored_responses: Iterable[int] = frozenset(), ignored_testcases: Iterable[tuple[str, str, int]] = frozenset(), response_validation: ValidationLevel = WARN, disable_server_validation: bool = True, mappings_path: str | Path = , invalid_property_default_response: int = 422, default_id_property_name: str = id, faker_locale: str | list[str] = , require_body_for_invalid_url: bool = False, recursion_limit: int = 1, recursion_default:
|
9
|
+
<arguments repr="source: str, origin: str = , base_path: str = , included_paths: Iterable[str] = frozenset(), ignored_paths: Iterable[str] = frozenset(), ignored_responses: Iterable[int] = frozenset(), ignored_testcases: Iterable[tuple[str, str, int]] = frozenset(), response_validation: ValidationLevel = WARN, disable_server_validation: bool = True, mappings_path: str | Path = , invalid_property_default_response: int = 422, default_id_property_name: str = id, faker_locale: str | list[str] = , require_body_for_invalid_url: bool = False, recursion_limit: int = 1, recursion_default: JSON = {}, username: str = , password: str = , security_token: str = , auth: AuthBase | None = None, cert: str | tuple[str, str] = , verify_tls: bool | str = True, extra_headers: Mapping[str, str] = {}, cookies: MutableMapping[str, str] | RequestsCookieJar | None = None, proxies: MutableMapping[str, str] | None = None">
|
10
10
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="source: str">
|
11
11
|
<name>source</name>
|
12
12
|
<type name="str" typedoc="string"/>
|
@@ -101,23 +101,9 @@
|
|
101
101
|
<type name="int" typedoc="integer"/>
|
102
102
|
<default>1</default>
|
103
103
|
</arg>
|
104
|
-
<arg kind="POSITIONAL_OR_NAMED" required="false" repr="recursion_default:
|
104
|
+
<arg kind="POSITIONAL_OR_NAMED" required="false" repr="recursion_default: JSON = {}">
|
105
105
|
<name>recursion_default</name>
|
106
|
-
<type name="Union" union="true">
|
107
|
-
<type name="dict" typedoc="dictionary">
|
108
|
-
<type name="str" typedoc="string"/>
|
109
106
|
<type name="JSON"/>
|
110
|
-
</type>
|
111
|
-
<type name="list" typedoc="list">
|
112
|
-
<type name="JSON"/>
|
113
|
-
</type>
|
114
|
-
<type name="str" typedoc="string"/>
|
115
|
-
<type name="bytes" typedoc="bytes"/>
|
116
|
-
<type name="int" typedoc="integer"/>
|
117
|
-
<type name="float" typedoc="float"/>
|
118
|
-
<type name="bool" typedoc="boolean"/>
|
119
|
-
<type name="None" typedoc="None"/>
|
120
|
-
</type>
|
121
107
|
<default>{}</default>
|
122
108
|
</arg>
|
123
109
|
<arg kind="POSITIONAL_OR_NAMED" required="false" repr="username: str = ">
|
@@ -261,7 +247,7 @@
|
|
261
247
|
</init>
|
262
248
|
</inits>
|
263
249
|
<keywords>
|
264
|
-
<kw name="Test Endpoint" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapi_executors.py" lineno="
|
250
|
+
<kw name="Test Endpoint" source="/workspaces/robotframework-openapitools/src/OpenApiDriver/openapi_executors.py" lineno="166">
|
265
251
|
<arguments repr="path: str, method: str, status_code: int">
|
266
252
|
<arg kind="POSITIONAL_OR_NAMED" required="true" repr="path: str">
|
267
253
|
<name>path</name>
|
@@ -350,17 +336,6 @@
|
|
350
336
|
<usage>__init__</usage>
|
351
337
|
</usages>
|
352
338
|
</type>
|
353
|
-
<type name="bytes" type="Standard">
|
354
|
-
<doc><p>Strings are converted to bytes so that each Unicode code point below 256 is directly mapped to a matching byte. Higher code points are not allowed. Robot Framework's <code>\xHH</code> escape syntax is convenient with bytes having non-printable values.</p>
|
355
|
-
<p>Examples: <code>good</code>, <code>hyvä</code> (same as <code>hyv\xE4</code>), <code>\x00</code> (the null byte)</p></doc>
|
356
|
-
<accepts>
|
357
|
-
<type>string</type>
|
358
|
-
<type>bytearray</type>
|
359
|
-
</accepts>
|
360
|
-
<usages>
|
361
|
-
<usage>__init__</usage>
|
362
|
-
</usages>
|
363
|
-
</type>
|
364
339
|
<type name="dictionary" type="Standard">
|
365
340
|
<doc><p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#dict">dictionary</a> literals. They are converted to actual dictionaries using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including dictionaries and other containers.</p>
|
366
341
|
<p>If the type has nested types like <code>dict[str, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
|
@@ -373,18 +348,6 @@
|
|
373
348
|
<usage>__init__</usage>
|
374
349
|
</usages>
|
375
350
|
</type>
|
376
|
-
<type name="float" type="Standard">
|
377
|
-
<doc><p>Conversion is done using Python's <a href="https://docs.python.org/library/functions.html#float">float</a> built-in function.</p>
|
378
|
-
<p>Starting from RF 4.1, spaces and underscores can be used as visual separators for digit grouping purposes.</p>
|
379
|
-
<p>Examples: <code>3.14</code>, <code>2.9979e8</code>, <code>10 000.000 01</code></p></doc>
|
380
|
-
<accepts>
|
381
|
-
<type>string</type>
|
382
|
-
<type>Real</type>
|
383
|
-
</accepts>
|
384
|
-
<usages>
|
385
|
-
<usage>__init__</usage>
|
386
|
-
</usages>
|
387
|
-
</type>
|
388
351
|
<type name="integer" type="Standard">
|
389
352
|
<doc><p>Conversion is done using Python's <a href="https://docs.python.org/library/functions.html#int">int</a> built-in function. Floating point numbers are accepted only if they can be represented as integers exactly. For example, <code>1.0</code> is accepted and <code>1.1</code> is not.</p>
|
390
353
|
<p>Starting from RF 4.1, it is possible to use hexadecimal, octal and binary numbers by prefixing values with <code>0x</code>, <code>0o</code> and <code>0b</code>, respectively.</p>
|
@@ -21,7 +21,6 @@ from OpenApiLibCore.dto_base import (
|
|
21
21
|
PropertyValueConstraint,
|
22
22
|
ResourceRelation,
|
23
23
|
UniquePropertyValueConstraint,
|
24
|
-
resolve_schema,
|
25
24
|
)
|
26
25
|
from OpenApiLibCore.dto_utils import DefaultDto
|
27
26
|
from OpenApiLibCore.openapi_libcore import (
|
@@ -52,5 +51,4 @@ __all__ = [
|
|
52
51
|
"ResourceRelation",
|
53
52
|
"UniquePropertyValueConstraint",
|
54
53
|
"ValidationLevel",
|
55
|
-
"resolve_schema",
|
56
54
|
]
|
@@ -3,10 +3,8 @@ Module holding the functions related to data generation
|
|
3
3
|
for the requests made as part of keyword exection.
|
4
4
|
"""
|
5
5
|
|
6
|
-
from .body_data_generation import get_json_data_for_dto_class
|
7
6
|
from .data_generation_core import get_request_data
|
8
7
|
|
9
8
|
__all__ = [
|
10
|
-
"get_json_data_for_dto_class",
|
11
9
|
"get_request_data",
|
12
10
|
]
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
8
8
|
|
9
9
|
from robot.api import logger
|
10
10
|
|
11
|
-
import OpenApiLibCore.path_functions as
|
11
|
+
import OpenApiLibCore.path_functions as _path_functions
|
12
12
|
from OpenApiLibCore.annotations import JSON
|
13
13
|
from OpenApiLibCore.dto_base import (
|
14
14
|
Dto,
|
@@ -16,26 +16,41 @@ from OpenApiLibCore.dto_base import (
|
|
16
16
|
PropertyValueConstraint,
|
17
17
|
)
|
18
18
|
from OpenApiLibCore.dto_utils import DefaultDto
|
19
|
+
from OpenApiLibCore.models import (
|
20
|
+
ArraySchema,
|
21
|
+
ObjectSchema,
|
22
|
+
SchemaObjectTypes,
|
23
|
+
UnionTypeSchema,
|
24
|
+
)
|
19
25
|
from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
|
20
26
|
from OpenApiLibCore.protocols import GetIdPropertyNameType
|
21
|
-
from OpenApiLibCore.value_utils import IGNORE
|
27
|
+
from OpenApiLibCore.value_utils import IGNORE
|
22
28
|
|
23
29
|
|
24
30
|
def get_json_data_for_dto_class(
|
25
|
-
schema:
|
31
|
+
schema: SchemaObjectTypes,
|
26
32
|
dto_class: type[Dto],
|
27
33
|
get_id_property_name: GetIdPropertyNameType,
|
28
|
-
operation_id: str =
|
34
|
+
operation_id: str | None = None,
|
29
35
|
) -> JSON:
|
30
|
-
|
31
|
-
|
36
|
+
if isinstance(schema, UnionTypeSchema):
|
37
|
+
chosen_schema = choice(schema.resolved_schemas)
|
38
|
+
return get_json_data_for_dto_class(
|
39
|
+
schema=chosen_schema,
|
40
|
+
dto_class=dto_class,
|
41
|
+
get_id_property_name=get_id_property_name,
|
42
|
+
operation_id=operation_id,
|
43
|
+
)
|
44
|
+
|
45
|
+
match schema:
|
46
|
+
case ObjectSchema():
|
32
47
|
return get_dict_data_for_dto_class(
|
33
48
|
schema=schema,
|
34
49
|
dto_class=dto_class,
|
35
50
|
get_id_property_name=get_id_property_name,
|
36
51
|
operation_id=operation_id,
|
37
52
|
)
|
38
|
-
case
|
53
|
+
case ArraySchema():
|
39
54
|
return get_list_data_for_dto_class(
|
40
55
|
schema=schema,
|
41
56
|
dto_class=dto_class,
|
@@ -43,22 +58,22 @@ def get_json_data_for_dto_class(
|
|
43
58
|
operation_id=operation_id,
|
44
59
|
)
|
45
60
|
case _:
|
46
|
-
return get_valid_value(
|
61
|
+
return schema.get_valid_value()
|
47
62
|
|
48
63
|
|
49
64
|
def get_dict_data_for_dto_class(
|
50
|
-
schema:
|
65
|
+
schema: ObjectSchema,
|
51
66
|
dto_class: type[Dto],
|
52
67
|
get_id_property_name: GetIdPropertyNameType,
|
53
|
-
operation_id: str =
|
68
|
+
operation_id: str | None = None,
|
54
69
|
) -> dict[str, Any]:
|
55
70
|
json_data: dict[str, Any] = {}
|
56
71
|
|
57
72
|
property_names = get_property_names_to_process(schema=schema, dto_class=dto_class)
|
58
73
|
|
59
74
|
for property_name in property_names:
|
60
|
-
property_schema = schema
|
61
|
-
if property_schema.
|
75
|
+
property_schema = schema.properties.root[property_name] # type: ignore[union-attr]
|
76
|
+
if property_schema.readOnly:
|
62
77
|
continue
|
63
78
|
|
64
79
|
json_data[property_name] = get_data_for_property(
|
@@ -73,15 +88,15 @@ def get_dict_data_for_dto_class(
|
|
73
88
|
|
74
89
|
|
75
90
|
def get_list_data_for_dto_class(
|
76
|
-
schema:
|
91
|
+
schema: ArraySchema,
|
77
92
|
dto_class: type[Dto],
|
78
93
|
get_id_property_name: GetIdPropertyNameType,
|
79
|
-
operation_id: str =
|
80
|
-
) -> list[
|
81
|
-
json_data: list[
|
82
|
-
list_item_schema = schema.
|
83
|
-
min_items = schema.
|
84
|
-
max_items = schema.
|
94
|
+
operation_id: str | None = None,
|
95
|
+
) -> list[JSON]:
|
96
|
+
json_data: list[JSON] = []
|
97
|
+
list_item_schema = schema.items
|
98
|
+
min_items = schema.minItems if schema.minItems is not None else 0
|
99
|
+
max_items = schema.maxItems if schema.maxItems is not None else 1
|
85
100
|
number_of_items_to_generate = randint(min_items, max_items)
|
86
101
|
for _ in range(number_of_items_to_generate):
|
87
102
|
list_item_data = get_json_data_for_dto_class(
|
@@ -96,29 +111,11 @@ def get_list_data_for_dto_class(
|
|
96
111
|
|
97
112
|
def get_data_for_property(
|
98
113
|
property_name: str,
|
99
|
-
property_schema:
|
114
|
+
property_schema: SchemaObjectTypes,
|
100
115
|
get_id_property_name: GetIdPropertyNameType,
|
101
116
|
dto_class: type[Dto],
|
102
|
-
operation_id: str,
|
117
|
+
operation_id: str | None,
|
103
118
|
) -> 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
119
|
if constrained_values := get_constrained_values(
|
123
120
|
dto_class=dto_class, property_name=property_name
|
124
121
|
):
|
@@ -144,32 +141,18 @@ def get_data_for_property(
|
|
144
141
|
) is not None:
|
145
142
|
return dependent_id
|
146
143
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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)
|
144
|
+
return get_json_data_for_dto_class(
|
145
|
+
schema=property_schema,
|
146
|
+
dto_class=DefaultDto,
|
147
|
+
get_id_property_name=get_id_property_name,
|
148
|
+
)
|
166
149
|
|
167
150
|
|
168
151
|
def get_value_constrained_by_nested_dto(
|
169
|
-
property_schema:
|
152
|
+
property_schema: SchemaObjectTypes,
|
170
153
|
nested_dto_class: type[Dto],
|
171
154
|
get_id_property_name: GetIdPropertyNameType,
|
172
|
-
operation_id: str,
|
155
|
+
operation_id: str | None,
|
173
156
|
) -> JSON:
|
174
157
|
nested_schema = get_schema_for_nested_dto(property_schema=property_schema)
|
175
158
|
nested_value = get_json_data_for_dto_class(
|
@@ -181,23 +164,21 @@ def get_value_constrained_by_nested_dto(
|
|
181
164
|
return nested_value
|
182
165
|
|
183
166
|
|
184
|
-
def get_schema_for_nested_dto(property_schema:
|
185
|
-
if property_schema
|
186
|
-
|
187
|
-
|
188
|
-
if possible_types := property_schema.get("types"):
|
189
|
-
return choice(possible_types)
|
167
|
+
def get_schema_for_nested_dto(property_schema: SchemaObjectTypes) -> SchemaObjectTypes:
|
168
|
+
if isinstance(property_schema, UnionTypeSchema):
|
169
|
+
chosen_schema = choice(property_schema.resolved_schemas)
|
170
|
+
return get_schema_for_nested_dto(chosen_schema)
|
190
171
|
|
191
|
-
|
172
|
+
return property_schema
|
192
173
|
|
193
174
|
|
194
175
|
def get_property_names_to_process(
|
195
|
-
schema:
|
176
|
+
schema: ObjectSchema,
|
196
177
|
dto_class: type[Dto],
|
197
178
|
) -> list[str]:
|
198
179
|
property_names = []
|
199
180
|
|
200
|
-
for property_name in schema.
|
181
|
+
for property_name in schema.properties.root: # type: ignore[union-attr]
|
201
182
|
# register the oas_name
|
202
183
|
_ = get_safe_name_for_oas_name(property_name)
|
203
184
|
if constrained_values := get_constrained_values(
|
@@ -208,9 +189,9 @@ def get_property_names_to_process(
|
|
208
189
|
continue
|
209
190
|
property_names.append(property_name)
|
210
191
|
|
211
|
-
max_properties = schema.
|
192
|
+
max_properties = schema.maxProperties
|
212
193
|
if max_properties and len(property_names) > max_properties:
|
213
|
-
required_properties = schema.
|
194
|
+
required_properties = schema.required
|
214
195
|
number_of_optional_properties = max_properties - len(required_properties)
|
215
196
|
optional_properties = [
|
216
197
|
name for name in property_names if name not in required_properties
|
@@ -239,7 +220,7 @@ def get_constrained_values(
|
|
239
220
|
def get_dependent_id(
|
240
221
|
dto_class: type[Dto],
|
241
222
|
property_name: str,
|
242
|
-
operation_id: str,
|
223
|
+
operation_id: str | None,
|
243
224
|
get_id_property_name: GetIdPropertyNameType,
|
244
225
|
) -> str | int | float | None:
|
245
226
|
relations = dto_class.get_relations()
|
@@ -262,7 +243,7 @@ def get_dependent_id(
|
|
262
243
|
except ValueError:
|
263
244
|
return None
|
264
245
|
|
265
|
-
valid_id =
|
246
|
+
valid_id = _path_functions.get_valid_id_for_path(
|
266
247
|
path=id_get_path, get_id_property_name=get_id_property_name
|
267
248
|
)
|
268
249
|
logger.debug(f"get_dependent_id for {id_get_path} returned {valid_id}")
|