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.
- OpenApiDriver/openapi_executors.py +15 -11
- OpenApiDriver/openapi_reader.py +12 -13
- OpenApiDriver/openapidriver.libspec +5 -42
- OpenApiLibCore/__init__.py +0 -2
- OpenApiLibCore/annotations.py +8 -1
- OpenApiLibCore/data_generation/__init__.py +0 -2
- OpenApiLibCore/data_generation/body_data_generation.py +54 -73
- OpenApiLibCore/data_generation/data_generation_core.py +75 -82
- OpenApiLibCore/data_invalidation.py +38 -25
- OpenApiLibCore/dto_base.py +48 -105
- OpenApiLibCore/dto_utils.py +31 -3
- OpenApiLibCore/localized_faker.py +88 -0
- OpenApiLibCore/models.py +723 -0
- OpenApiLibCore/openapi_libcore.libspec +48 -284
- OpenApiLibCore/openapi_libcore.py +54 -71
- OpenApiLibCore/parameter_utils.py +20 -14
- OpenApiLibCore/path_functions.py +10 -10
- OpenApiLibCore/path_invalidation.py +5 -7
- OpenApiLibCore/protocols.py +13 -5
- OpenApiLibCore/request_data.py +67 -102
- OpenApiLibCore/resource_relations.py +6 -5
- OpenApiLibCore/validation.py +50 -167
- OpenApiLibCore/value_utils.py +46 -358
- openapi_libgen/__init__.py +0 -46
- openapi_libgen/command_line.py +7 -19
- openapi_libgen/generator.py +84 -0
- openapi_libgen/parsing_utils.py +9 -5
- openapi_libgen/spec_parser.py +41 -114
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/METADATA +2 -1
- robotframework_openapitools-1.0.0b5.dist-info/RECORD +40 -0
- robotframework_openapitools-1.0.0b3.dist-info/RECORD +0 -37
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/LICENSE +0 -0
- {robotframework_openapitools-1.0.0b3.dist-info → robotframework_openapitools-1.0.0b5.dist-info}/WHEEL +0 -0
- {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
|
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
|
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:
|
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 =
|
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
|
-
|
47
|
-
|
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
|
-
|
61
|
+
operation_spec = OperationObject(operationId="")
|
52
62
|
|
53
63
|
parameters, params, headers = get_request_parameters(
|
54
|
-
dto_class=dto_class, method_spec=
|
64
|
+
dto_class=dto_class, method_spec=operation_spec
|
55
65
|
)
|
56
|
-
if
|
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=
|
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
|
-
|
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=
|
97
|
+
schema=body_schema,
|
75
98
|
dto_class=dto_class,
|
76
99
|
get_id_property_name=get_id_property_name,
|
77
|
-
operation_id=
|
100
|
+
operation_id=operation_spec.operationId,
|
78
101
|
)
|
79
102
|
dto_instance = _get_dto_instance_from_dto_data(
|
80
|
-
|
103
|
+
object_schema=body_schema,
|
81
104
|
dto_class=dto_class,
|
82
105
|
dto_data=dto_data,
|
83
|
-
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
|
-
|
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:
|
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=
|
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
|
-
|
137
|
+
object_schema: ObjectSchema,
|
114
138
|
dto_class: type[Dto],
|
115
139
|
dto_data: JSON,
|
116
|
-
method_spec:
|
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(
|
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=
|
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
|
-
|
137
|
-
) -> list[tuple[str, type[
|
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[
|
167
|
+
fields: list[tuple[str, type[object], Field[object]]] = []
|
168
|
+
|
140
169
|
for key, value in dto_data.items():
|
141
|
-
|
142
|
-
safe_key =
|
143
|
-
metadata = {"original_property_name": key}
|
144
|
-
if key in
|
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(
|
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
|
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:
|
204
|
-
) -> tuple[list[
|
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.
|
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.
|
209
|
-
header_params = [p for p in parameters if p.
|
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[
|
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
|
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
|
-
|
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
|
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
|
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.
|
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
|
39
|
+
if request_data.body_schema is None:
|
43
40
|
raise ValueError(
|
44
|
-
"Failed to invalidate:
|
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.
|
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.
|
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,
|
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.
|
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
|
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 =
|
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,
|
208
|
-
headers: dict[str,
|
209
|
-
parameter_data:
|
210
|
-
values_from_constraint: list[
|
211
|
-
) -> tuple[dict[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
|
-
|
224
|
-
|
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
|
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
|
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)
|