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

Make resource identifiers immutable #246

Merged
merged 3 commits into from
Sep 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

Next Release - (TBD)
--------------------

* bugfix:Identifier: Make resource identifiers immutable.
(`issue 246 <https://github.com/boto/boto3/pull/246>`__)


1.1.3 - 2015-09-03
------------------

Expand Down
4 changes: 2 additions & 2 deletions boto3/resources/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def __init__(self, *args, **kwargs):
# Allow setting identifiers as positional arguments in the order
# in which they were defined in the ResourceJSON.
for i, value in enumerate(args):
setattr(self, self.meta.identifiers[i], value)
setattr(self, '_' + self.meta.identifiers[i], value)

# Allow setting identifiers via keyword arguments. Here we need
# extra logic to ignore other keyword arguments like ``client``.
Expand All @@ -110,7 +110,7 @@ def __init__(self, *args, **kwargs):
if name not in self.meta.identifiers:
raise ValueError('Unknown keyword argument: {0}'.format(name))

setattr(self, name, value)
setattr(self, '_' + name, value)

# Validate that all identifiers have been set.
for identifier in self.meta.identifiers:
Expand Down
19 changes: 18 additions & 1 deletion boto3/resources/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ def _load_identifiers(self, attrs, meta, model):
"""
for identifier in model.identifiers:
meta.identifiers.append(identifier.name)
attrs[identifier.name] = None
attrs[identifier.name] = self._create_identifier(identifier.name)

def _load_actions(self, attrs, model, resource_defs, service_model):
"""
Expand Down Expand Up @@ -194,6 +194,23 @@ def _load_waiters(self, attrs, model):
for waiter in model.waiters:
attrs[waiter.name] = self._create_waiter(waiter)

def _create_identifier(factory_self, name):
"""
Creates a read-only property for identifier attributes.
"""
def identifier(self):
# The default value is set to ``None`` instead of
# raising an AttributeError because when resources are
# instantiated a check is made such that none of the
# identifiers have a value ``None``. If any are ``None``,
# a more informative user error than a generic AttributeError
# is raised.
return getattr(self, '_' + name, None)
Copy link
Member

Choose a reason for hiding this comment

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

Should we be returning None if the internal attr doesn't exist? Seems like that would only happen if we had a bug, in which case we'd want it to fail loudly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I am fine with that. I do not think that it will happen unless there is a bug. Furthermore, since this is an identifier in general, its value cannot be None so it make sense not to default to None.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Scratch what I said, I remembered why I set it a default to None. It is because when the resource is instantiated there is check to make sure that all of the identifiers are non-None values: https://github.com/boto/boto3/blob/develop/boto3/resources/base.py#L117. So if I did not use a default, I would get a bad error message like: 'test.Queue' object has no attribute '_url' instead of the current message of: ValueError: Required parameter url not set because the identifier getter is called before the ValueError is thrown. I would prefer to keep it as is unless you have any other suggestions.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah that's fine, but let's add a comment here that explains that.

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 can do that.


identifier.__name__ = str(name)
identifier.__doc__ = 'TODO'
return property(identifier)

def _create_autoload_property(factory_self, name, snake_cased):
"""
Creates a new property on the resource to lazy-load its value
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/resources/test_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,13 @@ def test_dangling_resource_raises_for_unknown_arg(self):
with self.assertRaises(ValueError):
resource.Queue(url='foo', bar='baz')

def test_dangling_resource_identifier_is_immutable(self):
resource = self.load('test', 'test', self.model, self.defs, None)()
queue = resource.Queue('url')
# We should not be able to change the identifier's value
with self.assertRaises(AttributeError):
queue.url = 'foo'

def test_dangling_resource_equality(self):
resource = self.load('test', 'test', self.model, self.defs, None)()

Expand Down