Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[python] readonly constructors #9409

Merged
merged 7 commits into from
May 11, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/generators/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ These options may be applied as additional-properties (cli) or configOptions (pl

| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default. NOTE: this option breaks composition and will be removed in 6.0.0</dd></dl>|false|
spacether marked this conversation as resolved.
Show resolved Hide resolved
|generateSourceCodeOnly|Specifies that only a library source code is to be generated.| |false|
|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
|library|library template (sub-template) to use: asyncio, tornado, urllib3| |urllib3|
Expand All @@ -29,6 +28,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl

| Type/Alias | Instantiated By |
| ---------- | --------------- |
|map|dict|


## LANGUAGE PRIMITIVES
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ from {{packageName}}.model_utils import ( # noqa: F401
none_type,
validate_get_composed_info,
)
from ..model_utils import OpenApiModel


{{#models}}
{{#model}}
{{#imports}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@classmethod
@convert_js_args_to_python_args
def from_openapi_data(cls, *args, **kwargs): # noqa: E501
Copy link
Contributor

@spacether spacether May 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if someone wanted to name an object property from_openapi_data?
How about using _from_openapi_data to reduce the likelihood of a naming collision?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess now I change the target branch to master since 5.2 was merged today

"""{{classname}} - a model defined in OpenAPI

Keyword Args:
{{#requiredVars}}
{{#defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/defaultValue}}
{{^defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}{{/description}}
{{/defaultValue}}
{{/requiredVars}}
{{> model_templates/docstring_init_required_kwargs }}
{{#optionalVars}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501
{{/optionalVars}}
"""

{{#requiredVars}}
{{#defaultValue}}
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
{{/defaultValue}}
{{/requiredVars}}
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())

self = super(OpenApiModel, cls).__new__(cls)

{{> model_templates/invalid_pos_args }}

self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)

constant_args = {
'_check_type': _check_type,
'_path_to_item': _path_to_item,
'_spec_property_naming': _spec_property_naming,
'_configuration': _configuration,
'_visited_composed_classes': self._visited_composed_classes,
}
composed_info = validate_get_composed_info(
constant_args, kwargs, self)
self._composed_instances = composed_info[0]
self._var_name_to_model_instances = composed_info[1]
self._additional_properties_model_instances = composed_info[2]
discarded_args = composed_info[3]

for var_name, var_value in kwargs.items():
if var_name in discarded_args and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self._additional_properties_model_instances:
# discard variable.
continue
setattr(self, var_name, var_value)

return self
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{{> model_templates/method_from_openapi_data_shared }}

{{#isEnum}}
self.value = value
{{/isEnum}}
{{#requiredVars}}
self.{{name}} = {{name}}
{{/requiredVars}}
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
self._configuration is not None and \
self._configuration.discard_unknown_keys and \
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
return self
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@classmethod
@convert_js_args_to_python_args
def from_openapi_data(cls{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs): # noqa: E501
"""{{classname}} - a model defined in OpenAPI

{{#requiredVars}}
{{#-first}}
Args:
{{/-first}}
{{^defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}{{/description}}
{{/defaultValue}}
{{#-last}}

{{/-last}}
{{/requiredVars}}
Keyword Args:
{{#requiredVars}}
{{#defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/defaultValue}}
{{/requiredVars}}
{{> model_templates/docstring_init_required_kwargs }}
{{#optionalVars}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} [optional]{{#defaultValue}} if omitted the server will use the default value of {{{defaultValue}}}{{/defaultValue}} # noqa: E501
{{/optionalVars}}
"""

{{#requiredVars}}
{{#defaultValue}}
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
{{/defaultValue}}
{{/requiredVars}}
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())

self = super(OpenApiModel, cls).__new__(cls)

{{> model_templates/invalid_pos_args }}

self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
@classmethod
@convert_js_args_to_python_args
def from_openapi_data(cls, *args, **kwargs):
"""{{classname}} - a model defined in OpenAPI

Note that value can be passed either in args or in kwargs, but not in both.

Args:
args[0] ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{defaultValue}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501

Keyword Args:
value ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}}{{#defaultValue}} if omitted defaults to {{{defaultValue}}}{{/defaultValue}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{> model_templates/docstring_init_required_kwargs }}
"""
# required up here when default value is not given
_path_to_item = kwargs.pop('_path_to_item', ())

self = super(OpenApiModel, cls).__new__(cls)

if 'value' in kwargs:
value = kwargs.pop('value')
elif args:
args = list(args)
value = args.pop(0)
{{#defaultValue}}
else:
value = {{{defaultValue}}}
{{/defaultValue}}
{{^defaultValue}}
else:
raise ApiTypeError(
"value is required, but not passed in args or kwargs and doesn't have default",
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
{{/defaultValue}}

_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())

{{> model_templates/invalid_pos_args }}

self._data_store = {}
self._check_type = _check_type
self._spec_property_naming = _spec_property_naming
self._path_to_item = _path_to_item
self._configuration = _configuration
self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
self.value = value
if kwargs:
raise ApiTypeError(
"Invalid named arguments=%s passed to %s. Remove those invalid named arguments." % (
kwargs,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)

return self
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@

Keyword Args:
{{#requiredVars}}
{{^isReadOnly}}
{{#defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/defaultValue}}
{{^defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}{{/description}}
{{/defaultValue}}
{{/isReadOnly}}
{{/requiredVars}}
{{> model_templates/docstring_init_required_kwargs }}
{{#optionalVars}}
Expand All @@ -30,25 +32,19 @@
"""

{{#requiredVars}}
{{^isReadOnly}}
{{#defaultValue}}
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
{{/defaultValue}}
{{/isReadOnly}}
{{/requiredVars}}
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())

if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
{{> model_templates/invalid_pos_args }}

self._data_store = {}
self._check_type = _check_type
Expand Down Expand Up @@ -78,4 +74,7 @@
self._additional_properties_model_instances:
# discard variable.
continue
setattr(self, var_name, var_value)
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise AttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
self.value = value
{{/isEnum}}
{{#requiredVars}}
{{^isReadOnly}}
self.{{name}} = {{name}}
{{/isReadOnly}}
{{/requiredVars}}
for var_name, var_value in kwargs.items():
if var_name not in self.attribute_map and \
Expand All @@ -22,4 +24,7 @@
self.additional_properties_type is None:
# discard variable.
continue
setattr(self, var_name, var_value)
setattr(self, var_name, var_value)
if var_name in self.read_only_vars:
raise AttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
f"class with read only attributes.")
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
@convert_js_args_to_python_args
def __init__(self{{#requiredVars}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/requiredVars}}, *args, **kwargs): # noqa: E501
def __init__(self{{#requiredVars}}{{^isReadOnly}}{{^defaultValue}}, {{name}}{{/defaultValue}}{{/isReadOnly}}{{/requiredVars}}, *args, **kwargs): # noqa: E501
"""{{classname}} - a model defined in OpenAPI

{{#requiredVars}}
{{^isReadOnly}}
{{#-first}}
Args:
{{/-first}}
Expand All @@ -12,12 +13,15 @@
{{#-last}}

{{/-last}}
{{/isReadOnly}}
{{/requiredVars}}
Keyword Args:
{{#requiredVars}}
{{^isReadOnly}}
{{#defaultValue}}
{{name}} ({{{dataType}}}):{{#description}} {{{description}}}.{{/description}} defaults to {{{defaultValue}}}{{#allowableValues}}, must be one of [{{#enumVars}}{{{value}}}, {{/enumVars}}]{{/allowableValues}} # noqa: E501
{{/defaultValue}}
{{/isReadOnly}}
{{/requiredVars}}
{{> model_templates/docstring_init_required_kwargs }}
{{#optionalVars}}
Expand All @@ -26,25 +30,19 @@
"""

{{#requiredVars}}
{{^isReadOnly}}
{{#defaultValue}}
{{name}} = kwargs.get('{{name}}', {{{defaultValue}}})
{{/defaultValue}}
{{/isReadOnly}}
{{/requiredVars}}
_check_type = kwargs.pop('_check_type', True)
_spec_property_naming = kwargs.pop('_spec_property_naming', False)
_path_to_item = kwargs.pop('_path_to_item', ())
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())

if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
{{> model_templates/invalid_pos_args }}

self._data_store = {}
self._check_type = _check_type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,7 @@
_configuration = kwargs.pop('_configuration', None)
_visited_composed_classes = kwargs.pop('_visited_composed_classes', ())

if args:
raise ApiTypeError(
"Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
args,
self.__class__.__name__,
),
path_to_item=_path_to_item,
valid_classes=(self.__class__,),
)
{{> model_templates/invalid_pos_args }}

self._data_store = {}
self._check_type = _check_type
Expand Down
Loading