From b70edd7f1b600b7927913528405de98ebbebf5ce Mon Sep 17 00:00:00 2001 From: Maksym Melnychok Date: Sun, 25 Oct 2020 17:13:22 +0100 Subject: [PATCH] [python] Add option to return None instead of raising exception when accessing unset attribute (#7784) * add option to return None instead of raising exception when accessing unset attribute * update python samples * reimplement getattr using getitem or get depending on attrNoneIfUnset * move getattr and setattr to respective templates * update docstrings, def get/setattr methods to have docstrings in them, use __dict__ to avoid recursion issues * revert required_properties change * add manual tests for .get method --- docs/generators/python-experimental.md | 1 + .../codegen/CodegenConstants.java | 3 + .../PythonClientExperimentalCodegen.java | 9 ++ .../methods_setattr_getattr_composed.mustache | 43 +++---- .../methods_setattr_getattr_normal.mustache | 26 ++--- .../model_templates/methods_shared.mustache | 18 +-- .../petstore_api/model_utils.py | 108 +++++++++--------- .../x_auth_id_alias/model_utils.py | 108 +++++++++--------- .../dynamic_servers/model_utils.py | 108 +++++++++--------- .../petstore_api/model_utils.py | 108 +++++++++--------- .../tests_manual/test_fruit.py | 3 + 11 files changed, 272 insertions(+), 263 deletions(-) diff --git a/docs/generators/python-experimental.md b/docs/generators/python-experimental.md index 309ab81e9a40..e3331950f954 100644 --- a/docs/generators/python-experimental.md +++ b/docs/generators/python-experimental.md @@ -14,6 +14,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl |packageUrl|python package URL.| |null| |packageVersion|python package version.| |1.0.0| |projectName|python project name in setup.py (e.g. petstore-api).| |null| +|pythonAttrNoneIfUnset|when accessing unset attribute, return `None` instead of raising `ApiAttributeError`| |false| |recursionLimit|Set the recursion limit. If not set, use the system default value.| |null| |useNose|use the nose test framework| |false| diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index 38528a6c6fea..04c0888e0a28 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -67,6 +67,9 @@ public class CodegenConstants { public static final String PYTHON_PACKAGE_NAME = "pythonPackageName"; public static final String PYTHON_PACKAGE_NAME_DESC = "package name for generated python code"; + public static final String PYTHON_ATTR_NONE_IF_UNSET = "pythonAttrNoneIfUnset"; + public static final String PYTHON_ATTR_NONE_IF_UNSET_DESC = "when accessing unset attribute, return `None` instead of raising `ApiAttributeError`"; + public static final String WITH_GO_CODEGEN_COMMENT = "withGoCodegenComment"; public static final String WITH_GO_CODEGEN_COMMENT_DESC = "whether to include Go codegen comment to disable Go Lint and collapse by default in GitHub PRs and diffs"; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java index 3ff8c20f4068..5d205c9bddf0 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientExperimentalCodegen.java @@ -122,6 +122,9 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { // optional params/props with **kwargs in python cliOptions.remove(4); + cliOptions.add(new CliOption(CodegenConstants.PYTHON_ATTR_NONE_IF_UNSET, CodegenConstants.PYTHON_ATTR_NONE_IF_UNSET_DESC) + .defaultValue(Boolean.FALSE.toString())); + generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata) .stability(Stability.EXPERIMENTAL) .build(); @@ -209,6 +212,12 @@ public class PythonClientExperimentalCodegen extends PythonClientCodegen { // default this to true so the python ModelSimple models will be generated ModelUtils.setGenerateAliasAsModel(true); LOGGER.info(CodegenConstants.GENERATE_ALIAS_AS_MODEL + " is hard coded to true in this generator. Alias models will only be generated if they contain validations or enums"); + + Boolean attrNoneIfUnset = false; + if (additionalProperties.containsKey(CodegenConstants.PYTHON_ATTR_NONE_IF_UNSET)) { + attrNoneIfUnset = Boolean.valueOf(additionalProperties.get(CodegenConstants.PYTHON_ATTR_NONE_IF_UNSET).toString()); + } + additionalProperties.put("attrNoneIfUnset", attrNoneIfUnset); } /** diff --git a/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_composed.mustache b/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_composed.mustache index 1a01fc472cd7..6b1013ca997d 100644 --- a/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_composed.mustache +++ b/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_composed.mustache @@ -1,5 +1,5 @@ - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return @@ -20,28 +20,22 @@ ) return None - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - path_to_item + [e for e in [self._path_to_item, name] if e] ) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + __unset_attribute_value__ = object() + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] # get the attribute from the correct instance model_instances = self._var_name_to_model_instances.get( name, self._additional_properties_model_instances) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) values = [] # A composed model stores child (oneof/anyOf/allOf) models under # self._var_name_to_model_instances. A named property can exist in @@ -55,11 +49,7 @@ values.append(v) len_values = len(values) if len_values == 0: - raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - path_to_item - ) + return default elif len_values == 1: return values[0] elif len_values > 1: @@ -67,11 +57,22 @@ "Values stored for property {0} in {1} differ when looking " "at self and self's composed instances. All values must be " "the same".format(name, type(self).__name__), - path_to_item + [e for e in [self._path_to_item, name] if e] ) + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + value = self.get(name, self.__unset_attribute_value__) + if value is self.__unset_attribute_value__: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + return value + def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ @@ -84,4 +85,4 @@ if name in model_instance._data_store: return True - return False + return False \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_normal.mustache b/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_normal.mustache index 32566dc684e6..e700dad95362 100644 --- a/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_normal.mustache +++ b/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_setattr_getattr_normal.mustache @@ -1,32 +1,32 @@ - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ - return name in self.__dict__['_data_store'] + return name in self.__dict__['_data_store'] \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_shared.mustache b/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_shared.mustache index d3cf66e41b64..43bcf6fd0d55 100644 --- a/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_shared.mustache +++ b/modules/openapi-generator/src/main/resources/python/python-experimental/model_templates/methods_shared.mustache @@ -1,15 +1,15 @@ - def __setitem__(self, name, value): - """this allows us to set values with instance[field_name] = val""" - self.__setattr__(name, value) - - def __getitem__(self, name): - """this allows us to get a value with val = instance[field_name]""" - return self.__getattr__(name) - def __repr__(self): """For `print` and `pprint`""" return self.to_str() def __ne__(self, other): """Returns true if both objects are not equal""" - return not self == other \ No newline at end of file + return not self == other + + def __setattr__(self, attr, value): + """set the value of an attribute using dot notation: `instance.attr = val`""" + self[attr] = value + + def __getattr__(self, attr): + """get the value of an attribute using dot notation: `instance.attr`""" + return self.{{#attrNoneIfUnset}}get{{/attrNoneIfUnset}}{{^attrNoneIfUnset}}__getitem__{{/attrNoneIfUnset}}(attr) \ No newline at end of file diff --git a/samples/client/petstore/python-experimental/petstore_api/model_utils.py b/samples/client/petstore/python-experimental/petstore_api/model_utils.py index 88a7e8ba38b9..e821ccb31b46 100644 --- a/samples/client/petstore/python-experimental/petstore_api/model_utils.py +++ b/samples/client/petstore/python-experimental/petstore_api/model_utils.py @@ -156,14 +156,6 @@ class OpenApiModel(object): ) self.__dict__['_data_store'][name] = value - def __setitem__(self, name, value): - """this allows us to set values with instance[field_name] = val""" - self.__setattr__(name, value) - - def __getitem__(self, name): - """this allows us to get a value with val = instance[field_name]""" - return self.__getattr__(name) - def __repr__(self): """For `print` and `pprint`""" return self.to_str() @@ -172,6 +164,14 @@ class OpenApiModel(object): """Returns true if both objects are not equal""" return not self == other + def __setattr__(self, attr, value): + """set the value of an attribute using dot notation: `instance.attr = val`""" + self[attr] = value + + def __getattr__(self, attr): + """get the value of an attribute using dot notation: `instance.attr`""" + return self.__getitem__(attr) + def __new__(cls, *args, **kwargs): # this function uses the discriminator to # pick a new schema/class to instantiate because a discriminator @@ -290,40 +290,39 @@ class ModelSimple(OpenApiModel): """the parent class of models whose type != object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_str(self): """Returns the string representation of the model""" return str(self.value) @@ -346,40 +345,39 @@ class ModelNormal(OpenApiModel): """the parent class of models whose type == object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) @@ -432,8 +430,8 @@ class ModelComposed(OpenApiModel): which contain the value that the key is referring to. """ - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return @@ -454,28 +452,22 @@ class ModelComposed(OpenApiModel): ) return None - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - path_to_item + [e for e in [self._path_to_item, name] if e] ) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + __unset_attribute_value__ = object() + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] # get the attribute from the correct instance model_instances = self._var_name_to_model_instances.get( name, self._additional_properties_model_instances) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) values = [] # A composed model stores child (oneof/anyOf/allOf) models under # self._var_name_to_model_instances. A named property can exist in @@ -489,11 +481,7 @@ class ModelComposed(OpenApiModel): values.append(v) len_values = len(values) if len_values == 0: - raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - path_to_item - ) + return default elif len_values == 1: return values[0] elif len_values > 1: @@ -501,11 +489,22 @@ class ModelComposed(OpenApiModel): "Values stored for property {0} in {1} differ when looking " "at self and self's composed instances. All values must be " "the same".format(name, type(self).__name__), - path_to_item + [e for e in [self._path_to_item, name] if e] ) + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + value = self.get(name, self.__unset_attribute_value__) + if value is self.__unset_attribute_value__: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + return value + def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ @@ -520,7 +519,6 @@ class ModelComposed(OpenApiModel): return False - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) diff --git a/samples/openapi3/client/extensions/x-auth-id-alias/python-experimental/x_auth_id_alias/model_utils.py b/samples/openapi3/client/extensions/x-auth-id-alias/python-experimental/x_auth_id_alias/model_utils.py index efdb666e7a79..594af400e44a 100644 --- a/samples/openapi3/client/extensions/x-auth-id-alias/python-experimental/x_auth_id_alias/model_utils.py +++ b/samples/openapi3/client/extensions/x-auth-id-alias/python-experimental/x_auth_id_alias/model_utils.py @@ -156,14 +156,6 @@ class OpenApiModel(object): ) self.__dict__['_data_store'][name] = value - def __setitem__(self, name, value): - """this allows us to set values with instance[field_name] = val""" - self.__setattr__(name, value) - - def __getitem__(self, name): - """this allows us to get a value with val = instance[field_name]""" - return self.__getattr__(name) - def __repr__(self): """For `print` and `pprint`""" return self.to_str() @@ -172,6 +164,14 @@ class OpenApiModel(object): """Returns true if both objects are not equal""" return not self == other + def __setattr__(self, attr, value): + """set the value of an attribute using dot notation: `instance.attr = val`""" + self[attr] = value + + def __getattr__(self, attr): + """get the value of an attribute using dot notation: `instance.attr`""" + return self.__getitem__(attr) + def __new__(cls, *args, **kwargs): # this function uses the discriminator to # pick a new schema/class to instantiate because a discriminator @@ -290,40 +290,39 @@ class ModelSimple(OpenApiModel): """the parent class of models whose type != object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_str(self): """Returns the string representation of the model""" return str(self.value) @@ -346,40 +345,39 @@ class ModelNormal(OpenApiModel): """the parent class of models whose type == object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) @@ -432,8 +430,8 @@ class ModelComposed(OpenApiModel): which contain the value that the key is referring to. """ - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return @@ -454,28 +452,22 @@ class ModelComposed(OpenApiModel): ) return None - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - path_to_item + [e for e in [self._path_to_item, name] if e] ) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + __unset_attribute_value__ = object() + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] # get the attribute from the correct instance model_instances = self._var_name_to_model_instances.get( name, self._additional_properties_model_instances) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) values = [] # A composed model stores child (oneof/anyOf/allOf) models under # self._var_name_to_model_instances. A named property can exist in @@ -489,11 +481,7 @@ class ModelComposed(OpenApiModel): values.append(v) len_values = len(values) if len_values == 0: - raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - path_to_item - ) + return default elif len_values == 1: return values[0] elif len_values > 1: @@ -501,11 +489,22 @@ class ModelComposed(OpenApiModel): "Values stored for property {0} in {1} differ when looking " "at self and self's composed instances. All values must be " "the same".format(name, type(self).__name__), - path_to_item + [e for e in [self._path_to_item, name] if e] ) + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + value = self.get(name, self.__unset_attribute_value__) + if value is self.__unset_attribute_value__: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + return value + def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ @@ -520,7 +519,6 @@ class ModelComposed(OpenApiModel): return False - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) diff --git a/samples/openapi3/client/features/dynamic-servers/python-experimental/dynamic_servers/model_utils.py b/samples/openapi3/client/features/dynamic-servers/python-experimental/dynamic_servers/model_utils.py index a50552534179..0963d9481807 100644 --- a/samples/openapi3/client/features/dynamic-servers/python-experimental/dynamic_servers/model_utils.py +++ b/samples/openapi3/client/features/dynamic-servers/python-experimental/dynamic_servers/model_utils.py @@ -156,14 +156,6 @@ class OpenApiModel(object): ) self.__dict__['_data_store'][name] = value - def __setitem__(self, name, value): - """this allows us to set values with instance[field_name] = val""" - self.__setattr__(name, value) - - def __getitem__(self, name): - """this allows us to get a value with val = instance[field_name]""" - return self.__getattr__(name) - def __repr__(self): """For `print` and `pprint`""" return self.to_str() @@ -172,6 +164,14 @@ class OpenApiModel(object): """Returns true if both objects are not equal""" return not self == other + def __setattr__(self, attr, value): + """set the value of an attribute using dot notation: `instance.attr = val`""" + self[attr] = value + + def __getattr__(self, attr): + """get the value of an attribute using dot notation: `instance.attr`""" + return self.__getitem__(attr) + def __new__(cls, *args, **kwargs): # this function uses the discriminator to # pick a new schema/class to instantiate because a discriminator @@ -290,40 +290,39 @@ class ModelSimple(OpenApiModel): """the parent class of models whose type != object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_str(self): """Returns the string representation of the model""" return str(self.value) @@ -346,40 +345,39 @@ class ModelNormal(OpenApiModel): """the parent class of models whose type == object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) @@ -432,8 +430,8 @@ class ModelComposed(OpenApiModel): which contain the value that the key is referring to. """ - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return @@ -454,28 +452,22 @@ class ModelComposed(OpenApiModel): ) return None - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - path_to_item + [e for e in [self._path_to_item, name] if e] ) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + __unset_attribute_value__ = object() + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] # get the attribute from the correct instance model_instances = self._var_name_to_model_instances.get( name, self._additional_properties_model_instances) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) values = [] # A composed model stores child (oneof/anyOf/allOf) models under # self._var_name_to_model_instances. A named property can exist in @@ -489,11 +481,7 @@ class ModelComposed(OpenApiModel): values.append(v) len_values = len(values) if len_values == 0: - raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - path_to_item - ) + return default elif len_values == 1: return values[0] elif len_values > 1: @@ -501,11 +489,22 @@ class ModelComposed(OpenApiModel): "Values stored for property {0} in {1} differ when looking " "at self and self's composed instances. All values must be " "the same".format(name, type(self).__name__), - path_to_item + [e for e in [self._path_to_item, name] if e] ) + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + value = self.get(name, self.__unset_attribute_value__) + if value is self.__unset_attribute_value__: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + return value + def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ @@ -520,7 +519,6 @@ class ModelComposed(OpenApiModel): return False - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) diff --git a/samples/openapi3/client/petstore/python-experimental/petstore_api/model_utils.py b/samples/openapi3/client/petstore/python-experimental/petstore_api/model_utils.py index 88a7e8ba38b9..e821ccb31b46 100644 --- a/samples/openapi3/client/petstore/python-experimental/petstore_api/model_utils.py +++ b/samples/openapi3/client/petstore/python-experimental/petstore_api/model_utils.py @@ -156,14 +156,6 @@ class OpenApiModel(object): ) self.__dict__['_data_store'][name] = value - def __setitem__(self, name, value): - """this allows us to set values with instance[field_name] = val""" - self.__setattr__(name, value) - - def __getitem__(self, name): - """this allows us to get a value with val = instance[field_name]""" - return self.__getattr__(name) - def __repr__(self): """For `print` and `pprint`""" return self.to_str() @@ -172,6 +164,14 @@ class OpenApiModel(object): """Returns true if both objects are not equal""" return not self == other + def __setattr__(self, attr, value): + """set the value of an attribute using dot notation: `instance.attr = val`""" + self[attr] = value + + def __getattr__(self, attr): + """get the value of an attribute using dot notation: `instance.attr`""" + return self.__getitem__(attr) + def __new__(cls, *args, **kwargs): # this function uses the discriminator to # pick a new schema/class to instantiate because a discriminator @@ -290,40 +290,39 @@ class ModelSimple(OpenApiModel): """the parent class of models whose type != object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_str(self): """Returns the string representation of the model""" return str(self.value) @@ -346,40 +345,39 @@ class ModelNormal(OpenApiModel): """the parent class of models whose type == object in their swagger/openapi""" - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return self.set_attribute(name, value) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] - if name in self.__dict__['_data_store']: - return self.__dict__['_data_store'][name] + return self.__dict__['_data_store'].get(name, default) + + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + if name in self: + return self.get(name) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - [name] + [e for e in [self._path_to_item, name] if e] ) def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ return name in self.__dict__['_data_store'] - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) @@ -432,8 +430,8 @@ class ModelComposed(OpenApiModel): which contain the value that the key is referring to. """ - def __setattr__(self, name, value): - """this allows us to set a value with instance.field_name = val""" + def __setitem__(self, name, value): + """set the value of an attribute using square-bracket notation: `instance[attr] = val`""" if name in self.required_properties: self.__dict__[name] = value return @@ -454,28 +452,22 @@ class ModelComposed(OpenApiModel): ) return None - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) raise ApiAttributeError( "{0} has no attribute '{1}'".format( type(self).__name__, name), - path_to_item + [e for e in [self._path_to_item, name] if e] ) - def __getattr__(self, name): - """this allows us to get a value with val = instance.field_name""" + __unset_attribute_value__ = object() + + def get(self, name, default=None): + """returns the value of an attribute or some default value if the attribute was not set""" if name in self.required_properties: return self.__dict__[name] # get the attribute from the correct instance model_instances = self._var_name_to_model_instances.get( name, self._additional_properties_model_instances) - path_to_item = [] - if self._path_to_item: - path_to_item.extend(self._path_to_item) - path_to_item.append(name) values = [] # A composed model stores child (oneof/anyOf/allOf) models under # self._var_name_to_model_instances. A named property can exist in @@ -489,11 +481,7 @@ class ModelComposed(OpenApiModel): values.append(v) len_values = len(values) if len_values == 0: - raise ApiAttributeError( - "{0} has no attribute '{1}'".format( - type(self).__name__, name), - path_to_item - ) + return default elif len_values == 1: return values[0] elif len_values > 1: @@ -501,11 +489,22 @@ class ModelComposed(OpenApiModel): "Values stored for property {0} in {1} differ when looking " "at self and self's composed instances. All values must be " "the same".format(name, type(self).__name__), - path_to_item + [e for e in [self._path_to_item, name] if e] ) + def __getitem__(self, name): + """get the value of an attribute using square-bracket notation: `instance[attr]`""" + value = self.get(name, self.__unset_attribute_value__) + if value is self.__unset_attribute_value__: + raise ApiAttributeError( + "{0} has no attribute '{1}'".format( + type(self).__name__, name), + [e for e in [self._path_to_item, name] if e] + ) + return value + def __contains__(self, name): - """this allows us to use `in` operator: `'attr' in instance`""" + """used by `in` operator to check if an attrbute value was set in an instance: `'attr' in instance`""" if name in self.required_properties: return name in self.__dict__ @@ -520,7 +519,6 @@ class ModelComposed(OpenApiModel): return False - def to_dict(self): """Returns the model properties as a dict""" return model_to_dict(self, serialize=False) diff --git a/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fruit.py b/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fruit.py index fac0319663e6..a60fdb9f6be3 100644 --- a/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fruit.py +++ b/samples/openapi3/client/petstore/python-experimental/tests_manual/test_fruit.py @@ -47,6 +47,7 @@ class TestFruit(unittest.TestCase): # check its properties self.assertEqual(fruit.length_cm, length_cm) self.assertEqual(fruit['length_cm'], length_cm) + self.assertEqual(fruit.get('length_cm'), length_cm) self.assertEqual(getattr(fruit, 'length_cm'), length_cm) self.assertEqual(fruit.color, color) self.assertEqual(fruit['color'], color) @@ -85,6 +86,8 @@ class TestFruit(unittest.TestCase): # Per Python doc, if the named attribute does not exist, # default is returned if provided. self.assertEqual(getattr(fruit, 'cultivar', 'some value'), 'some value') + self.assertEqual(fruit.get('cultivar'), None) + self.assertEqual(fruit.get('cultivar', 'some value'), 'some value') # Per Python doc, if the named attribute does not exist, # default is returned if provided, otherwise AttributeError is raised.