Skip to content

Commit 1adf7f0

Browse files
authored
[python] readonly constructors (#9409)
* readonly * other tests * doc * python samples * model utils
1 parent 083338c commit 1adf7f0

14 files changed

+400
-51
lines changed

.generator/templates/model.mustache

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ from {{packageName}}.model_utils import ( # noqa: F401
1717
none_type,
1818
validate_get_composed_info,
1919
)
20+
from ..model_utils import OpenApiModel
21+
from {{packageName}}.exceptions import ApiAttributeError
22+
2023
{{#models}}
2124
{{#model}}
2225
{{#imports}}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
if args:
2+
raise ApiTypeError(
3+
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
4+
args,
5+
self.__class__.__name__,
6+
),
7+
path_to_item=_path_to_item,
8+
valid_classes=(self.__class__,),
9+
)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
@classmethod
2+
@convert_js_args_to_python_args
3+
def _from_openapi_data(cls, *args, **kwargs): # noqa: E501
4+
"""{{classname}} - a model defined in OpenAPI
5+
6+
Keyword Args:
7+
{{#requiredVars}}
8+
{{#defaultValue}}
9+
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
10+
{{/defaultValue}}
11+
{{^defaultValue}}
12+
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}{{/description}}
13+
{{/defaultValue}}
14+
{{/requiredVars}}
15+
{{> model_templates/docstring_init_required_kwargs }}
16+
{{#optionalVars}}
17+
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501
18+
{{/optionalVars}}
19+
"""
20+
21+
{{#requiredVars}}
22+
{{#defaultValue}}
23+
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
24+
{{/defaultValue}}
25+
{{/requiredVars}}
26+
_check_type = kwargs.pop('_check_type', True)
27+
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
28+
_path_to_item = kwargs.pop('_path_to_item', ())
29+
_configuration = kwargs.pop('_configuration', None)
30+
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
31+
32+
self = super(OpenApiModel, cls).__new__(cls)
33+
34+
{{> model_templates/invalid_pos_args }}
35+
36+
self._data_store = {}
37+
self._check_type = _check_type
38+
self._spec_property_naming = _spec_property_naming
39+
self._path_to_item = _path_to_item
40+
self._configuration = _configuration
41+
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
42+
43+
constant_args = {
44+
'_check_type': _check_type,
45+
'_path_to_item': _path_to_item,
46+
'_spec_property_naming': _spec_property_naming,
47+
'_configuration': _configuration,
48+
'_visited_composed_classes': self._visited_composed_classes,
49+
}
50+
composed_info = validate_get_composed_info(
51+
constant_args, kwargs, self)
52+
self._composed_instances = composed_info[0]
53+
self._var_name_to_model_instances = composed_info[1]
54+
self._additional_properties_model_instances = composed_info[2]
55+
discarded_args = composed_info[3]
56+
57+
for var_name, var_value in kwargs.items():
58+
if var_name in discarded_args and \
59+
self._configuration is not None and \
60+
self._configuration.discard_unknown_keys and \
61+
self._additional_properties_model_instances:
62+
# discard variable.
63+
continue
64+
setattr(self, var_name, var_value)
65+
66+
return self
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{{> model_templates/method_from_openapi_data_shared }}
2+
3+
{{#isEnum}}
4+
self.value = value
5+
{{/isEnum}}
6+
{{#requiredVars}}
7+
self.{{name}} = {{name}}
8+
{{/requiredVars}}
9+
for var_name, var_value in kwargs.items():
10+
if var_name not in self.attribute_map and \
11+
self._configuration is not None and \
12+
self._configuration.discard_unknown_keys and \
13+
self.additional_properties_type is None:
14+
# discard variable.
15+
continue
16+
setattr(self, var_name, var_value)
17+
return self
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@classmethod
2+
@convert_js_args_to_python_args
3+
def _from_openapi_data(cls{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs): # noqa: E501
4+
"""{{classname}} - a model defined in OpenAPI
5+
6+
{{#requiredVars}}
7+
{{#-first}}
8+
Args:
9+
{{/-first}}
10+
{{^defaultValue}}
11+
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}{{/description}}
12+
{{/defaultValue}}
13+
{{#-last}}
14+
15+
{{/-last}}
16+
{{/requiredVars}}
17+
Keyword Args:
18+
{{#requiredVars}}
19+
{{#defaultValue}}
20+
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
21+
{{/defaultValue}}
22+
{{/requiredVars}}
23+
{{> model_templates/docstring_init_required_kwargs }}
24+
{{#optionalVars}}
25+
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501
26+
{{/optionalVars}}
27+
"""
28+
29+
{{#requiredVars}}
30+
{{#defaultValue}}
31+
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
32+
{{/defaultValue}}
33+
{{/requiredVars}}
34+
_check_type = kwargs.pop('_check_type', True)
35+
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
36+
_path_to_item = kwargs.pop('_path_to_item', ())
37+
_configuration = kwargs.pop('_configuration', None)
38+
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
39+
40+
self = super(OpenApiModel, cls).__new__(cls)
41+
42+
{{> model_templates/invalid_pos_args }}
43+
44+
self._data_store = {}
45+
self._check_type = _check_type
46+
self._spec_property_naming = _spec_property_naming
47+
self._path_to_item = _path_to_item
48+
self._configuration = _configuration
49+
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
@classmethod
2+
@convert_js_args_to_python_args
3+
def _from_openapi_data(cls, *args, **kwargs):
4+
"""{{classname}} - a model defined in OpenAPI
5+
6+
Note that value can be passed either in args or in kwargs, but not in both.
7+
8+
Args:
9+
args[0] ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{defaultValue}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
10+
11+
Keyword Args:
12+
value ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{defaultValue}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
13+
{{> model_templates/docstring_init_required_kwargs }}
14+
"""
15+
# required up here when default value is not given
16+
_path_to_item = kwargs.pop('_path_to_item', ())
17+
18+
self = super(OpenApiModel, cls).__new__(cls)
19+
20+
if 'value' in kwargs:
21+
value = kwargs.pop('value')
22+
elif args:
23+
args = list(args)
24+
value = args.pop(0)
25+
{{#defaultValue}}
26+
else:
27+
value = {{{defaultValue}}}
28+
{{/defaultValue}}
29+
{{^defaultValue}}
30+
else:
31+
raise ApiTypeError(
32+
"value is required, but not passed in args or kwargs and doesn't have default",
33+
path_to_item=_path_to_item,
34+
valid_classes=(self.__class__,),
35+
)
36+
{{/defaultValue}}
37+
38+
_check_type = kwargs.pop('_check_type', True)
39+
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
40+
_configuration = kwargs.pop('_configuration', None)
41+
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
42+
43+
{{> model_templates/invalid_pos_args }}
44+
45+
self._data_store = {}
46+
self._check_type = _check_type
47+
self._spec_property_naming = _spec_property_naming
48+
self._path_to_item = _path_to_item
49+
self._configuration = _configuration
50+
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
51+
self.value = value
52+
if kwargs:
53+
raise ApiTypeError(
54+
"Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
55+
kwargs,
56+
self.__class__.__name__,
57+
),
58+
path_to_item=_path_to_item,
59+
valid_classes=(self.__class__,),
60+
)
61+
62+
return self

.generator/templates/model_templates/method_init_composed.mustache

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
Keyword Args:
1818
{{#requiredVars}}
19+
{{^isReadOnly}}
1920
{{#defaultValue}}
2021
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
2122
{{/defaultValue}}
2223
{{^defaultValue}}
2324
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}{{/description}}
2425
{{/defaultValue}}
26+
{{/isReadOnly}}
2527
{{/requiredVars}}
2628
{{> model_templates/docstring_init_required_kwargs }}
2729
{{#optionalVars}}
@@ -30,25 +32,19 @@
3032
"""
3133

3234
{{#requiredVars}}
35+
{{^isReadOnly}}
3336
{{#defaultValue}}
3437
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
3538
{{/defaultValue}}
39+
{{/isReadOnly}}
3640
{{/requiredVars}}
3741
_check_type = kwargs.pop('_check_type', True)
3842
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
3943
_path_to_item = kwargs.pop('_path_to_item', ())
4044
_configuration = kwargs.pop('_configuration', None)
4145
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
4246

43-
if args:
44-
raise ApiTypeError(
45-
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
46-
args,
47-
self.__class__.__name__,
48-
),
49-
path_to_item=_path_to_item,
50-
valid_classes=(self.__class__,),
51-
)
47+
{{> model_templates/invalid_pos_args }}
5248

5349
self._data_store = {}
5450
self._check_type = _check_type
@@ -78,4 +74,7 @@
7874
self._additional_properties_model_instances:
7975
# discard variable.
8076
continue
81-
setattr(self, var_name, var_value)
77+
setattr(self, var_name, var_value)
78+
if var_name in self.read_only_vars:
79+
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
80+
f"class with read only attributes.")

.generator/templates/model_templates/method_init_normal.mustache

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
self.value = value
1414
{{/isEnum}}
1515
{{#requiredVars}}
16+
{{^isReadOnly}}
1617
self.{{name}} = {{name}}
18+
{{/isReadOnly}}
1719
{{/requiredVars}}
1820
for var_name, var_value in kwargs.items():
1921
if var_name not in self.attribute_map and \
@@ -22,4 +24,7 @@
2224
self.additional_properties_type is None:
2325
# discard variable.
2426
continue
25-
setattr(self, var_name, var_value)
27+
setattr(self, var_name, var_value)
28+
if var_name in self.read_only_vars:
29+
raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
30+
f"class with read only attributes.")

.generator/templates/model_templates/method_init_shared.mustache

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
@convert_js_args_to_python_args
2-
def __init__(self{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs): # noqa: E501
2+
def __init__(self{{#requiredVars}}{{^isReadOnly}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/isReadOnly}}{{/requiredVars}}, *args, **kwargs): # noqa: E501
33
"""{{classname}} - a model defined in OpenAPI
44

55
{{#requiredVars}}
6+
{{^isReadOnly}}
67
{{#-first}}
78
Args:
89
{{/-first}}
@@ -12,12 +13,15 @@
1213
{{#-last}}
1314

1415
{{/-last}}
16+
{{/isReadOnly}}
1517
{{/requiredVars}}
1618
Keyword Args:
1719
{{#requiredVars}}
20+
{{^isReadOnly}}
1821
{{#defaultValue}}
1922
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
2023
{{/defaultValue}}
24+
{{/isReadOnly}}
2125
{{/requiredVars}}
2226
{{> model_templates/docstring_init_required_kwargs }}
2327
{{#optionalVars}}
@@ -26,25 +30,19 @@
2630
"""
2731

2832
{{#requiredVars}}
33+
{{^isReadOnly}}
2934
{{#defaultValue}}
3035
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
3136
{{/defaultValue}}
37+
{{/isReadOnly}}
3238
{{/requiredVars}}
3339
_check_type = kwargs.pop('_check_type', True)
3440
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
3541
_path_to_item = kwargs.pop('_path_to_item', ())
3642
_configuration = kwargs.pop('_configuration', None)
3743
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
3844

39-
if args:
40-
raise ApiTypeError(
41-
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
42-
args,
43-
self.__class__.__name__,
44-
),
45-
path_to_item=_path_to_item,
46-
valid_classes=(self.__class__,),
47-
)
45+
{{> model_templates/invalid_pos_args }}
4846

4947
self._data_store = {}
5048
self._check_type = _check_type

.generator/templates/model_templates/method_init_simple.mustache

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,7 @@
4646
_configuration = kwargs.pop('_configuration', None)
4747
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
4848

49-
if args:
50-
raise ApiTypeError(
51-
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
52-
args,
53-
self.__class__.__name__,
54-
),
55-
path_to_item=_path_to_item,
56-
valid_classes=(self.__class__,),
57-
)
49+
{{> model_templates/invalid_pos_args }}
5850

5951
self._data_store = {}
6052
self._check_type = _check_type

0 commit comments

Comments
 (0)