Skip to content

Commit

Permalink
Merge pull request boto#7 from boto/model
Browse files Browse the repository at this point in the history
Add a resource model abstraction. Fixes boto#7.
  • Loading branch information
danielgtaylor committed Oct 23, 2014
2 parents e47ef42 + 8c03ed0 commit 9e6bc37
Show file tree
Hide file tree
Showing 13 changed files with 654 additions and 207 deletions.
22 changes: 10 additions & 12 deletions boto3/resources/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class ServiceAction(object):
The action may construct parameters from existing resource identifiers
and may return either a raw response or a new resource instance.
:type action_def: dict
:param action_def: The action definition.
:type action_model: :py:class`~boto3.resources.model.Action`
:param action_model: The action model.
:type factory: ResourceFactory
:param factory: The factory that created the resource class to which
this action is attached.
Expand All @@ -39,19 +39,18 @@ class ServiceAction(object):
:type service_model: :ref:`botocore.model.ServiceModel`
:param service_model: The Botocore service model
"""
def __init__(self, action_def, factory=None, resource_defs=None,
def __init__(self, action_model, factory=None, resource_defs=None,
service_model=None):
self.action_def = action_def
self.action_model = action_model

search_path = action_def.get('path')
search_path = action_model.path

# In the simplest case we just return the response, but if a
# resource is defined, then we must create these before returning.
response_resource_def = action_def.get('resource', {})
if response_resource_def:
if action_model.resource:
self.response_handler = ResourceHandler(search_path, factory,
resource_defs, service_model, response_resource_def,
action_def.get('request', {}).get('operation'))
resource_defs, service_model, action_model.resource,
action_model.request.operation)
else:
self.response_handler = RawHandler(search_path)

Expand All @@ -65,13 +64,12 @@ def __call__(self, parent, *args, **kwargs):
:rtype: dict or ServiceResource or list(ServiceResource)
:return: The response, either as a raw dict or resource instance(s).
"""
request_def = self.action_def.get('request', {})
operation_name = xform_name(request_def.get('operation', ''))
operation_name = xform_name(self.action_model.request.operation)

# First, build predefined params and then update with the
# user-supplied kwargs, which allows overriding the pre-built
# params if needed.
params = create_request_parameters(parent, request_def)
params = create_request_parameters(parent, self.action_model.request)
params.update(kwargs)

logger.info('Calling %s:%s with %r', parent.meta['service_name'],
Expand Down
35 changes: 17 additions & 18 deletions boto3/resources/collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,19 @@ class ResourceCollection(object):
See :ref:`guide_collections` for a high-level overview of collections,
including when remote service requests are performed.
:type definition: dict
:param definition: Collection definition
:type model: :py:class:`~boto3.resources.model.Collection`
:param model: Collection model
:type parent: :py:class:`~boto3.resources.base.ServiceResource`
:param parent: The collection's parent resource
:type handler: :py:class:`~boto3.resources.response.ResourceHandler`
:param handler: The resource response handler used to create resource
instances
"""
def __init__(self, definition, parent, handler, **kwargs):
self._definition = definition
def __init__(self, model, parent, handler, **kwargs):
self._model = model
self._parent = parent
self._py_operation_name = xform_name(
definition.get('request', {}).get('operation', ''))
model.request.operation)
self._handler = handler
self._params = kwargs

Expand All @@ -54,7 +54,7 @@ def __repr__(self):
self._parent,
'{0}.{1}'.format(
self._parent.meta['service_name'],
self._definition.get('resource', {}).get('type')
self._model.resource.type
)
)

Expand All @@ -70,7 +70,7 @@ def __iter__(self):
page_size = cleaned_params.pop('page_size', None)

params = create_request_parameters(
self._parent, self._definition.get('request', {}))
self._parent, self._model.request)
params.update(cleaned_params)

# Is this a paginated operation? If so, we need to get an
Expand Down Expand Up @@ -123,7 +123,7 @@ def _clone(self, **kwargs):
"""
params = copy.deepcopy(self._params)
params.update(kwargs)
clone = self.__class__(self._definition, self._parent,
clone = self.__class__(self._model, self._parent,
self._handler, **params)
return clone

Expand Down Expand Up @@ -224,8 +224,8 @@ class CollectionManager(object):
See :ref:`guide_collections` for a high-level overview of collections,
including when remote service requests are performed.
:type collection_def: dict
:param collection_def: Collection definition
:type model: :py:class:`~boto3.resources.model.Collection`
:param model: Collection model
:type parent: :py:class:`~boto3.resources.base.ServiceResource`
:param parent: The collection's parent resource
:type factory: :py:class:`~boto3.resources.factory.ResourceFactory`
Expand All @@ -235,24 +235,23 @@ class CollectionManager(object):
:type service_model: :ref:`botocore.model.ServiceModel`
:param service_model: The Botocore service model
"""
def __init__(self, collection_def, parent, factory, resource_defs,
def __init__(self, model, parent, factory, resource_defs,
service_model):
self._definition = collection_def
operation_name = self._definition.get('request', {}).get('operation')
self._model = model
operation_name = self._model.request.operation
self._parent = parent

search_path = collection_def.get('path', '')
response_resource_def = collection_def.get('resource')
search_path = model.path
self._handler = ResourceHandler(search_path, factory, resource_defs,
service_model, response_resource_def, operation_name)
service_model, model.resource, operation_name)

def __repr__(self):
return '{0}({1}, {2})'.format(
self.__class__.__name__,
self._parent,
'{0}.{1}'.format(
self._parent.meta['service_name'],
self._definition.get('resource', {}).get('type')
self._model.resource.type
)
)

Expand All @@ -263,7 +262,7 @@ def iterator(self, **kwargs):
:rtype: :py:class:`ResourceCollection`
:return: An iterable representing the collection of resources
"""
return ResourceCollection(self._definition, self._parent,
return ResourceCollection(self._model, self._parent,
self._handler, **kwargs)

# Set up some methods to proxy ResourceCollection methods
Expand Down
73 changes: 39 additions & 34 deletions boto3/resources/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .action import ServiceAction
from .base import ServiceResource
from .collection import CollectionManager
from .model import ResourceModel
from ..compat import json, OrderedDict
from ..exceptions import ResourceLoadException

Expand Down Expand Up @@ -137,12 +138,16 @@ def load_from_definition(self, service_name, resource_name, model,
'meta': meta,
}

self._load_identifiers(attrs, meta, model)
self._load_subresources(attrs, service_name, resource_name, model,
resource_defs, service_model)
self._load_actions(attrs, model, resource_defs, service_model)
self._load_attributes(attrs, meta, model, service_model)
self._load_collections(attrs, model, resource_defs, service_model)
resource_model = ResourceModel(resource_name, model, resource_defs)

self._load_identifiers(attrs, meta, resource_model)
self._load_subresources(attrs, service_name, resource_name,
resource_model, resource_defs, service_model)
self._load_actions(attrs, resource_model, resource_defs,
service_model)
self._load_attributes(attrs, meta, resource_model, service_model)
self._load_collections(attrs, resource_model, resource_defs,
service_model)

# Create the name based on the requested service and resource
cls_name = resource_name
Expand All @@ -157,8 +162,8 @@ def _load_identifiers(self, attrs, meta, model):
the resource cannot be used. Identifiers become arguments for
operations on the resource.
"""
for identifier in model.get('identifiers', []):
snake_cased = xform_name(identifier['name'])
for identifier in model.identifiers:
snake_cased = xform_name(identifier.name)
self._check_allowed_name(attrs, snake_cased)
meta['identifiers'].append(snake_cased)
attrs[snake_cased] = None
Expand All @@ -175,35 +180,35 @@ def _load_subresources(self, attrs, service_name, resource_name,
# This is a service, so dangle all the resource_defs as if
# they were subresources of the service itself.
for name, resource_def in resource_defs.items():
cls = self.load_from_definition(service_name, name,
resource_defs.get(name), resource_defs, service_model)
cls = self.load_from_definition(
service_name, name, resource_defs.get(name, {}),
resource_defs, service_model)
attrs[name] = self._create_class_partial(cls)

# For non-services, subresources are explicitly listed
sub_resources = model.get('subResources', {})
if sub_resources:
identifiers = sub_resources.get('identifiers', {})
for name in sub_resources.get('resources'):
klass = self.load_from_definition(service_name, name,
resource_defs.get(name), resource_defs, service_model)
attrs[name] = self._create_class_partial(klass,
identifiers=identifiers)
if model.sub_resources:
identifiers = model.sub_resources.identifiers
for name in model.sub_resources.resource_names:
cls = self.load_from_definition(
service_name, name, resource_defs.get(name, {}),
resource_defs, service_model)
attrs[name] = self._create_class_partial(
cls, identifiers=identifiers)

def _load_actions(self, attrs, model, resource_defs, service_model):
"""
Actions on the resource become methods, with the ``load`` method
being a special case which sets internal data for attributes, and
``reload`` is an alias for ``load``.
"""
if 'load' in model:
load_def = model.get('load')

attrs['load'] = self._create_action('load',
load_def, resource_defs, service_model, is_load=True)
if model.load:
attrs['load'] = self._create_action(
'load', model.load, resource_defs, service_model,
is_load=True)
attrs['reload'] = attrs['load']

for name, action in model.get('actions', {}).items():
snake_cased = xform_name(name)
for action in model.actions:
snake_cased = xform_name(action.name)
self._check_allowed_name(attrs, snake_cased)
attrs[snake_cased] = self._create_action(snake_cased,
action, resource_defs, service_model)
Expand All @@ -215,8 +220,8 @@ def _load_attributes(self, attrs, meta, model, service_model):
is defined in the Botocore service JSON, hence the need for
access to the ``service_model``.
"""
if 'shape' in model:
shape = service_model.shape_for(model.get('shape'))
if model.shape:
shape = service_model.shape_for(model.shape)

for name, member in shape.members.items():
snake_cased = xform_name(name)
Expand All @@ -229,12 +234,12 @@ def _load_attributes(self, attrs, meta, model, service_model):
snake_cased)

def _load_collections(self, attrs, model, resource_defs, service_model):
for name, definition in model.get('hasMany', {}).items():
snake_cased = xform_name(name)
for collection_model in model.collections:
snake_cased = xform_name(collection_model.name)
self._check_allowed_name(attrs, snake_cased)

attrs[snake_cased] = self._create_collection(snake_cased,
definition, resource_defs, service_model)
collection_model, resource_defs, service_model)

def _check_allowed_name(self, attrs, name):
"""
Expand Down Expand Up @@ -274,13 +279,13 @@ def property_loader(self):
property_loader.__doc__ = 'TODO'
return property(property_loader)

def _create_collection(factory_self, snake_cased, collection_def,
def _create_collection(factory_self, snake_cased, collection_model,
resource_defs, service_model):
"""
Creates a new property on the resource to lazy-load a collection.
"""
def get_collection(self):
return CollectionManager(collection_def,
return CollectionManager(collection_model,
self, factory_self, resource_defs, service_model)

get_collection.__name__ = str(snake_cased)
Expand Down Expand Up @@ -328,7 +333,7 @@ def create_resource(self, *args, **kwargs):
create_resource.__doc__ = doc.format(resource_cls)
return create_resource

def _create_action(factory_self, snake_cased, action_def, resource_defs,
def _create_action(factory_self, snake_cased, action_model, resource_defs,
service_model, is_load=False):
"""
Creates a new method which makes a request to the underlying
Expand All @@ -337,7 +342,7 @@ def _create_action(factory_self, snake_cased, action_def, resource_defs,
# Create the action in in this closure but before the ``do_action``
# method below is invoked, which allows instances of the resource
# to share the ServiceAction instance.
action = ServiceAction(action_def, factory=factory_self,
action = ServiceAction(action_model, factory=factory_self,
resource_defs=resource_defs, service_model=service_model)

# A resource's ``load`` method is special because it sets
Expand Down
Loading

0 comments on commit 9e6bc37

Please sign in to comment.