aboutsummaryrefslogtreecommitdiff
path: root/.venv/lib/python3.12/site-packages/boto3/resources/factory.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/boto3/resources/factory.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are hereHEADmaster
Diffstat (limited to '.venv/lib/python3.12/site-packages/boto3/resources/factory.py')
-rw-r--r--.venv/lib/python3.12/site-packages/boto3/resources/factory.py601
1 files changed, 601 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/boto3/resources/factory.py b/.venv/lib/python3.12/site-packages/boto3/resources/factory.py
new file mode 100644
index 00000000..4cdd2f01
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/boto3/resources/factory.py
@@ -0,0 +1,601 @@
+# Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"). You
+# may not use this file except in compliance with the License. A copy of
+# the License is located at
+#
+# https://aws.amazon.com/apache2.0/
+#
+# or in the "license" file accompanying this file. This file is
+# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
+# ANY KIND, either express or implied. See the License for the specific
+# language governing permissions and limitations under the License.
+
+import logging
+from functools import partial
+
+from ..docs import docstring
+from ..exceptions import ResourceLoadException
+from .action import ServiceAction, WaiterAction
+from .base import ResourceMeta, ServiceResource
+from .collection import CollectionFactory
+from .model import ResourceModel
+from .response import ResourceHandler, build_identifiers
+
+logger = logging.getLogger(__name__)
+
+
+class ResourceFactory:
+ """
+ A factory to create new :py:class:`~boto3.resources.base.ServiceResource`
+ classes from a :py:class:`~boto3.resources.model.ResourceModel`. There are
+ two types of lookups that can be done: one on the service itself (e.g. an
+ SQS resource) and another on models contained within the service (e.g. an
+ SQS Queue resource).
+ """
+
+ def __init__(self, emitter):
+ self._collection_factory = CollectionFactory()
+ self._emitter = emitter
+
+ def load_from_definition(
+ self, resource_name, single_resource_json_definition, service_context
+ ):
+ """
+ Loads a resource from a model, creating a new
+ :py:class:`~boto3.resources.base.ServiceResource` subclass
+ with the correct properties and methods, named based on the service
+ and resource name, e.g. EC2.Instance.
+
+ :type resource_name: string
+ :param resource_name: Name of the resource to look up. For services,
+ this should match the ``service_name``.
+
+ :type single_resource_json_definition: dict
+ :param single_resource_json_definition:
+ The loaded json of a single service resource or resource
+ definition.
+
+ :type service_context: :py:class:`~boto3.utils.ServiceContext`
+ :param service_context: Context about the AWS service
+
+ :rtype: Subclass of :py:class:`~boto3.resources.base.ServiceResource`
+ :return: The service or resource class.
+ """
+ logger.debug(
+ 'Loading %s:%s', service_context.service_name, resource_name
+ )
+
+ # Using the loaded JSON create a ResourceModel object.
+ resource_model = ResourceModel(
+ resource_name,
+ single_resource_json_definition,
+ service_context.resource_json_definitions,
+ )
+
+ # Do some renaming of the shape if there was a naming collision
+ # that needed to be accounted for.
+ shape = None
+ if resource_model.shape:
+ shape = service_context.service_model.shape_for(
+ resource_model.shape
+ )
+ resource_model.load_rename_map(shape)
+
+ # Set some basic info
+ meta = ResourceMeta(
+ service_context.service_name, resource_model=resource_model
+ )
+ attrs = {
+ 'meta': meta,
+ }
+
+ # Create and load all of attributes of the resource class based
+ # on the models.
+
+ # Identifiers
+ self._load_identifiers(
+ attrs=attrs,
+ meta=meta,
+ resource_name=resource_name,
+ resource_model=resource_model,
+ )
+
+ # Load/Reload actions
+ self._load_actions(
+ attrs=attrs,
+ resource_name=resource_name,
+ resource_model=resource_model,
+ service_context=service_context,
+ )
+
+ # Attributes that get auto-loaded
+ self._load_attributes(
+ attrs=attrs,
+ meta=meta,
+ resource_name=resource_name,
+ resource_model=resource_model,
+ service_context=service_context,
+ )
+
+ # Collections and their corresponding methods
+ self._load_collections(
+ attrs=attrs,
+ resource_model=resource_model,
+ service_context=service_context,
+ )
+
+ # References and Subresources
+ self._load_has_relations(
+ attrs=attrs,
+ resource_name=resource_name,
+ resource_model=resource_model,
+ service_context=service_context,
+ )
+
+ # Waiter resource actions
+ self._load_waiters(
+ attrs=attrs,
+ resource_name=resource_name,
+ resource_model=resource_model,
+ service_context=service_context,
+ )
+
+ # Create the name based on the requested service and resource
+ cls_name = resource_name
+ if service_context.service_name == resource_name:
+ cls_name = 'ServiceResource'
+ cls_name = service_context.service_name + '.' + cls_name
+
+ base_classes = [ServiceResource]
+ if self._emitter is not None:
+ self._emitter.emit(
+ f'creating-resource-class.{cls_name}',
+ class_attributes=attrs,
+ base_classes=base_classes,
+ service_context=service_context,
+ )
+ return type(str(cls_name), tuple(base_classes), attrs)
+
+ def _load_identifiers(self, attrs, meta, resource_model, resource_name):
+ """
+ Populate required identifiers. These are arguments without which
+ the resource cannot be used. Identifiers become arguments for
+ operations on the resource.
+ """
+ for identifier in resource_model.identifiers:
+ meta.identifiers.append(identifier.name)
+ attrs[identifier.name] = self._create_identifier(
+ identifier, resource_name
+ )
+
+ def _load_actions(
+ self, attrs, resource_name, resource_model, service_context
+ ):
+ """
+ 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 resource_model.load:
+ attrs['load'] = self._create_action(
+ action_model=resource_model.load,
+ resource_name=resource_name,
+ service_context=service_context,
+ is_load=True,
+ )
+ attrs['reload'] = attrs['load']
+
+ for action in resource_model.actions:
+ attrs[action.name] = self._create_action(
+ action_model=action,
+ resource_name=resource_name,
+ service_context=service_context,
+ )
+
+ def _load_attributes(
+ self, attrs, meta, resource_name, resource_model, service_context
+ ):
+ """
+ Load resource attributes based on the resource shape. The shape
+ name is referenced in the resource JSON, but the shape itself
+ is defined in the Botocore service JSON, hence the need for
+ access to the ``service_model``.
+ """
+ if not resource_model.shape:
+ return
+
+ shape = service_context.service_model.shape_for(resource_model.shape)
+
+ identifiers = {
+ i.member_name: i
+ for i in resource_model.identifiers
+ if i.member_name
+ }
+ attributes = resource_model.get_attributes(shape)
+ for name, (orig_name, member) in attributes.items():
+ if name in identifiers:
+ prop = self._create_identifier_alias(
+ resource_name=resource_name,
+ identifier=identifiers[name],
+ member_model=member,
+ service_context=service_context,
+ )
+ else:
+ prop = self._create_autoload_property(
+ resource_name=resource_name,
+ name=orig_name,
+ snake_cased=name,
+ member_model=member,
+ service_context=service_context,
+ )
+ attrs[name] = prop
+
+ def _load_collections(self, attrs, resource_model, service_context):
+ """
+ Load resource collections from the model. Each collection becomes
+ a :py:class:`~boto3.resources.collection.CollectionManager` instance
+ on the resource instance, which allows you to iterate and filter
+ through the collection's items.
+ """
+ for collection_model in resource_model.collections:
+ attrs[collection_model.name] = self._create_collection(
+ resource_name=resource_model.name,
+ collection_model=collection_model,
+ service_context=service_context,
+ )
+
+ def _load_has_relations(
+ self, attrs, resource_name, resource_model, service_context
+ ):
+ """
+ Load related resources, which are defined via a ``has``
+ relationship but conceptually come in two forms:
+
+ 1. A reference, which is a related resource instance and can be
+ ``None``, such as an EC2 instance's ``vpc``.
+ 2. A subresource, which is a resource constructor that will always
+ return a resource instance which shares identifiers/data with
+ this resource, such as ``s3.Bucket('name').Object('key')``.
+ """
+ for reference in resource_model.references:
+ # This is a dangling reference, i.e. we have all
+ # the data we need to create the resource, so
+ # this instance becomes an attribute on the class.
+ attrs[reference.name] = self._create_reference(
+ reference_model=reference,
+ resource_name=resource_name,
+ service_context=service_context,
+ )
+
+ for subresource in resource_model.subresources:
+ # This is a sub-resource class you can create
+ # by passing in an identifier, e.g. s3.Bucket(name).
+ attrs[subresource.name] = self._create_class_partial(
+ subresource_model=subresource,
+ resource_name=resource_name,
+ service_context=service_context,
+ )
+
+ self._create_available_subresources_command(
+ attrs, resource_model.subresources
+ )
+
+ def _create_available_subresources_command(self, attrs, subresources):
+ _subresources = [subresource.name for subresource in subresources]
+ _subresources = sorted(_subresources)
+
+ def get_available_subresources(factory_self):
+ """
+ Returns a list of all the available sub-resources for this
+ Resource.
+
+ :returns: A list containing the name of each sub-resource for this
+ resource
+ :rtype: list of str
+ """
+ return _subresources
+
+ attrs['get_available_subresources'] = get_available_subresources
+
+ def _load_waiters(
+ self, attrs, resource_name, resource_model, service_context
+ ):
+ """
+ Load resource waiters from the model. Each waiter allows you to
+ wait until a resource reaches a specific state by polling the state
+ of the resource.
+ """
+ for waiter in resource_model.waiters:
+ attrs[waiter.name] = self._create_waiter(
+ resource_waiter_model=waiter,
+ resource_name=resource_name,
+ service_context=service_context,
+ )
+
+ def _create_identifier(factory_self, identifier, resource_name):
+ """
+ Creates a read-only property for identifier attributes.
+ """
+
+ def get_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, '_' + identifier.name, None)
+
+ get_identifier.__name__ = str(identifier.name)
+ get_identifier.__doc__ = docstring.IdentifierDocstring(
+ resource_name=resource_name,
+ identifier_model=identifier,
+ include_signature=False,
+ )
+
+ return property(get_identifier)
+
+ def _create_identifier_alias(
+ factory_self, resource_name, identifier, member_model, service_context
+ ):
+ """
+ Creates a read-only property that aliases an identifier.
+ """
+
+ def get_identifier(self):
+ return getattr(self, '_' + identifier.name, None)
+
+ get_identifier.__name__ = str(identifier.member_name)
+ get_identifier.__doc__ = docstring.AttributeDocstring(
+ service_name=service_context.service_name,
+ resource_name=resource_name,
+ attr_name=identifier.member_name,
+ event_emitter=factory_self._emitter,
+ attr_model=member_model,
+ include_signature=False,
+ )
+
+ return property(get_identifier)
+
+ def _create_autoload_property(
+ factory_self,
+ resource_name,
+ name,
+ snake_cased,
+ member_model,
+ service_context,
+ ):
+ """
+ Creates a new property on the resource to lazy-load its value
+ via the resource's ``load`` method (if it exists).
+ """
+
+ # The property loader will check to see if this resource has already
+ # been loaded and return the cached value if possible. If not, then
+ # it first checks to see if it CAN be loaded (raise if not), then
+ # calls the load before returning the value.
+ def property_loader(self):
+ if self.meta.data is None:
+ if hasattr(self, 'load'):
+ self.load()
+ else:
+ raise ResourceLoadException(
+ f'{self.__class__.__name__} has no load method'
+ )
+
+ return self.meta.data.get(name)
+
+ property_loader.__name__ = str(snake_cased)
+ property_loader.__doc__ = docstring.AttributeDocstring(
+ service_name=service_context.service_name,
+ resource_name=resource_name,
+ attr_name=snake_cased,
+ event_emitter=factory_self._emitter,
+ attr_model=member_model,
+ include_signature=False,
+ )
+
+ return property(property_loader)
+
+ def _create_waiter(
+ factory_self, resource_waiter_model, resource_name, service_context
+ ):
+ """
+ Creates a new wait method for each resource where both a waiter and
+ resource model is defined.
+ """
+ waiter = WaiterAction(
+ resource_waiter_model,
+ waiter_resource_name=resource_waiter_model.name,
+ )
+
+ def do_waiter(self, *args, **kwargs):
+ waiter(self, *args, **kwargs)
+
+ do_waiter.__name__ = str(resource_waiter_model.name)
+ do_waiter.__doc__ = docstring.ResourceWaiterDocstring(
+ resource_name=resource_name,
+ event_emitter=factory_self._emitter,
+ service_model=service_context.service_model,
+ resource_waiter_model=resource_waiter_model,
+ service_waiter_model=service_context.service_waiter_model,
+ include_signature=False,
+ )
+ return do_waiter
+
+ def _create_collection(
+ factory_self, resource_name, collection_model, service_context
+ ):
+ """
+ Creates a new property on the resource to lazy-load a collection.
+ """
+ cls = factory_self._collection_factory.load_from_definition(
+ resource_name=resource_name,
+ collection_model=collection_model,
+ service_context=service_context,
+ event_emitter=factory_self._emitter,
+ )
+
+ def get_collection(self):
+ return cls(
+ collection_model=collection_model,
+ parent=self,
+ factory=factory_self,
+ service_context=service_context,
+ )
+
+ get_collection.__name__ = str(collection_model.name)
+ get_collection.__doc__ = docstring.CollectionDocstring(
+ collection_model=collection_model, include_signature=False
+ )
+ return property(get_collection)
+
+ def _create_reference(
+ factory_self, reference_model, resource_name, service_context
+ ):
+ """
+ Creates a new property on the resource to lazy-load a reference.
+ """
+ # References are essentially an action with no request
+ # or response, so we can re-use the response handlers to
+ # build up resources from identifiers and data members.
+ handler = ResourceHandler(
+ search_path=reference_model.resource.path,
+ factory=factory_self,
+ resource_model=reference_model.resource,
+ service_context=service_context,
+ )
+
+ # Are there any identifiers that need access to data members?
+ # This is important when building the resource below since
+ # it requires the data to be loaded.
+ needs_data = any(
+ i.source == 'data' for i in reference_model.resource.identifiers
+ )
+
+ def get_reference(self):
+ # We need to lazy-evaluate the reference to handle circular
+ # references between resources. We do this by loading the class
+ # when first accessed.
+ # This is using a *response handler* so we need to make sure
+ # our data is loaded (if possible) and pass that data into
+ # the handler as if it were a response. This allows references
+ # to have their data loaded properly.
+ if needs_data and self.meta.data is None and hasattr(self, 'load'):
+ self.load()
+ return handler(self, {}, self.meta.data)
+
+ get_reference.__name__ = str(reference_model.name)
+ get_reference.__doc__ = docstring.ReferenceDocstring(
+ reference_model=reference_model, include_signature=False
+ )
+ return property(get_reference)
+
+ def _create_class_partial(
+ factory_self, subresource_model, resource_name, service_context
+ ):
+ """
+ Creates a new method which acts as a functools.partial, passing
+ along the instance's low-level `client` to the new resource
+ class' constructor.
+ """
+ name = subresource_model.resource.type
+
+ def create_resource(self, *args, **kwargs):
+ # We need a new method here because we want access to the
+ # instance's client.
+ positional_args = []
+
+ # We lazy-load the class to handle circular references.
+ json_def = service_context.resource_json_definitions.get(name, {})
+ resource_cls = factory_self.load_from_definition(
+ resource_name=name,
+ single_resource_json_definition=json_def,
+ service_context=service_context,
+ )
+
+ # Assumes that identifiers are in order, which lets you do
+ # e.g. ``sqs.Queue('foo').Message('bar')`` to create a new message
+ # linked with the ``foo`` queue and which has a ``bar`` receipt
+ # handle. If we did kwargs here then future positional arguments
+ # would lead to failure.
+ identifiers = subresource_model.resource.identifiers
+ if identifiers is not None:
+ for identifier, value in build_identifiers(identifiers, self):
+ positional_args.append(value)
+
+ return partial(
+ resource_cls, *positional_args, client=self.meta.client
+ )(*args, **kwargs)
+
+ create_resource.__name__ = str(name)
+ create_resource.__doc__ = docstring.SubResourceDocstring(
+ resource_name=resource_name,
+ sub_resource_model=subresource_model,
+ service_model=service_context.service_model,
+ include_signature=False,
+ )
+ return create_resource
+
+ def _create_action(
+ factory_self,
+ action_model,
+ resource_name,
+ service_context,
+ is_load=False,
+ ):
+ """
+ Creates a new method which makes a request to the underlying
+ AWS service.
+ """
+ # 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_model, factory=factory_self, service_context=service_context
+ )
+
+ # A resource's ``load`` method is special because it sets
+ # values on the resource instead of returning the response.
+ if is_load:
+ # We need a new method here because we want access to the
+ # instance via ``self``.
+ def do_action(self, *args, **kwargs):
+ response = action(self, *args, **kwargs)
+ self.meta.data = response
+
+ # Create the docstring for the load/reload methods.
+ lazy_docstring = docstring.LoadReloadDocstring(
+ action_name=action_model.name,
+ resource_name=resource_name,
+ event_emitter=factory_self._emitter,
+ load_model=action_model,
+ service_model=service_context.service_model,
+ include_signature=False,
+ )
+ else:
+ # We need a new method here because we want access to the
+ # instance via ``self``.
+ def do_action(self, *args, **kwargs):
+ response = action(self, *args, **kwargs)
+
+ if hasattr(self, 'load'):
+ # Clear cached data. It will be reloaded the next
+ # time that an attribute is accessed.
+ # TODO: Make this configurable in the future?
+ self.meta.data = None
+
+ return response
+
+ lazy_docstring = docstring.ActionDocstring(
+ resource_name=resource_name,
+ event_emitter=factory_self._emitter,
+ action_model=action_model,
+ service_model=service_context.service_model,
+ include_signature=False,
+ )
+
+ do_action.__name__ = str(action_model.name)
+ do_action.__doc__ = lazy_docstring
+ return do_action