about summary refs log tree commit diff
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-4a52a71956a8d46fcb7294ac71734504bb09bcc2.tar.gz
two version of R2R are here HEAD master
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