about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/botocore/hooks.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/botocore/hooks.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/botocore/hooks.py')
-rw-r--r--.venv/lib/python3.12/site-packages/botocore/hooks.py660
1 files changed, 660 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/botocore/hooks.py b/.venv/lib/python3.12/site-packages/botocore/hooks.py
new file mode 100644
index 00000000..583cb39c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/botocore/hooks.py
@@ -0,0 +1,660 @@
+# Copyright 2012-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
+#
+# http://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 copy
+import logging
+from collections import deque, namedtuple
+
+from botocore.compat import accepts_kwargs
+from botocore.utils import EVENT_ALIASES
+
+logger = logging.getLogger(__name__)
+
+
+_NodeList = namedtuple('NodeList', ['first', 'middle', 'last'])
+_FIRST = 0
+_MIDDLE = 1
+_LAST = 2
+
+
+class NodeList(_NodeList):
+    def __copy__(self):
+        first_copy = copy.copy(self.first)
+        middle_copy = copy.copy(self.middle)
+        last_copy = copy.copy(self.last)
+        copied = NodeList(first_copy, middle_copy, last_copy)
+        return copied
+
+
+def first_non_none_response(responses, default=None):
+    """Find first non None response in a list of tuples.
+
+    This function can be used to find the first non None response from
+    handlers connected to an event.  This is useful if you are interested
+    in the returned responses from event handlers. Example usage::
+
+        print(first_non_none_response([(func1, None), (func2, 'foo'),
+                                       (func3, 'bar')]))
+        # This will print 'foo'
+
+    :type responses: list of tuples
+    :param responses: The responses from the ``EventHooks.emit`` method.
+        This is a list of tuples, and each tuple is
+        (handler, handler_response).
+
+    :param default: If no non-None responses are found, then this default
+        value will be returned.
+
+    :return: The first non-None response in the list of tuples.
+
+    """
+    for response in responses:
+        if response[1] is not None:
+            return response[1]
+    return default
+
+
+class BaseEventHooks:
+    def emit(self, event_name, **kwargs):
+        """Call all handlers subscribed to an event.
+
+        :type event_name: str
+        :param event_name: The name of the event to emit.
+
+        :type **kwargs: dict
+        :param **kwargs: Arbitrary kwargs to pass through to the
+            subscribed handlers.  The ``event_name`` will be injected
+            into the kwargs so it's not necessary to add this to **kwargs.
+
+        :rtype: list of tuples
+        :return: A list of ``(handler_func, handler_func_return_value)``
+
+        """
+        return []
+
+    def register(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        """Register an event handler for a given event.
+
+        If a ``unique_id`` is given, the handler will not be registered
+        if a handler with the ``unique_id`` has already been registered.
+
+        Handlers are called in the order they have been registered.
+        Note handlers can also be registered with ``register_first()``
+        and ``register_last()``.  All handlers registered with
+        ``register_first()`` are called before handlers registered
+        with ``register()`` which are called before handlers registered
+        with ``register_last()``.
+
+        """
+        self._verify_and_register(
+            event_name,
+            handler,
+            unique_id,
+            register_method=self._register,
+            unique_id_uses_count=unique_id_uses_count,
+        )
+
+    def register_first(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        """Register an event handler to be called first for an event.
+
+        All event handlers registered with ``register_first()`` will
+        be called before handlers registered with ``register()`` and
+        ``register_last()``.
+
+        """
+        self._verify_and_register(
+            event_name,
+            handler,
+            unique_id,
+            register_method=self._register_first,
+            unique_id_uses_count=unique_id_uses_count,
+        )
+
+    def register_last(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        """Register an event handler to be called last for an event.
+
+        All event handlers registered with ``register_last()`` will be called
+        after handlers registered with ``register_first()`` and ``register()``.
+
+        """
+        self._verify_and_register(
+            event_name,
+            handler,
+            unique_id,
+            register_method=self._register_last,
+            unique_id_uses_count=unique_id_uses_count,
+        )
+
+    def _verify_and_register(
+        self,
+        event_name,
+        handler,
+        unique_id,
+        register_method,
+        unique_id_uses_count,
+    ):
+        self._verify_is_callable(handler)
+        self._verify_accept_kwargs(handler)
+        register_method(event_name, handler, unique_id, unique_id_uses_count)
+
+    def unregister(
+        self,
+        event_name,
+        handler=None,
+        unique_id=None,
+        unique_id_uses_count=False,
+    ):
+        """Unregister an event handler for a given event.
+
+        If no ``unique_id`` was given during registration, then the
+        first instance of the event handler is removed (if the event
+        handler has been registered multiple times).
+
+        """
+        pass
+
+    def _verify_is_callable(self, func):
+        if not callable(func):
+            raise ValueError(f"Event handler {func} must be callable.")
+
+    def _verify_accept_kwargs(self, func):
+        """Verifies a callable accepts kwargs
+
+        :type func: callable
+        :param func: A callable object.
+
+        :returns: True, if ``func`` accepts kwargs, otherwise False.
+
+        """
+        try:
+            if not accepts_kwargs(func):
+                raise ValueError(
+                    f"Event handler {func} must accept keyword "
+                    f"arguments (**kwargs)"
+                )
+        except TypeError:
+            return False
+
+
+class HierarchicalEmitter(BaseEventHooks):
+    def __init__(self):
+        # We keep a reference to the handlers for quick
+        # read only access (we never modify self._handlers).
+        # A cache of event name to handler list.
+        self._lookup_cache = {}
+        self._handlers = _PrefixTrie()
+        # This is used to ensure that unique_id's are only
+        # registered once.
+        self._unique_id_handlers = {}
+
+    def _emit(self, event_name, kwargs, stop_on_response=False):
+        """
+        Emit an event with optional keyword arguments.
+
+        :type event_name: string
+        :param event_name: Name of the event
+        :type kwargs: dict
+        :param kwargs: Arguments to be passed to the handler functions.
+        :type stop_on_response: boolean
+        :param stop_on_response: Whether to stop on the first non-None
+                                response. If False, then all handlers
+                                will be called. This is especially useful
+                                to handlers which mutate data and then
+                                want to stop propagation of the event.
+        :rtype: list
+        :return: List of (handler, response) tuples from all processed
+                 handlers.
+        """
+        responses = []
+        # Invoke the event handlers from most specific
+        # to least specific, each time stripping off a dot.
+        handlers_to_call = self._lookup_cache.get(event_name)
+        if handlers_to_call is None:
+            handlers_to_call = self._handlers.prefix_search(event_name)
+            self._lookup_cache[event_name] = handlers_to_call
+        elif not handlers_to_call:
+            # Short circuit and return an empty response is we have
+            # no handlers to call.  This is the common case where
+            # for the majority of signals, nothing is listening.
+            return []
+        kwargs['event_name'] = event_name
+        responses = []
+        for handler in handlers_to_call:
+            logger.debug('Event %s: calling handler %s', event_name, handler)
+            response = handler(**kwargs)
+            responses.append((handler, response))
+            if stop_on_response and response is not None:
+                return responses
+        return responses
+
+    def emit(self, event_name, **kwargs):
+        """
+        Emit an event by name with arguments passed as keyword args.
+
+            >>> responses = emitter.emit(
+            ...     'my-event.service.operation', arg1='one', arg2='two')
+
+        :rtype: list
+        :return: List of (handler, response) tuples from all processed
+                 handlers.
+        """
+        return self._emit(event_name, kwargs)
+
+    def emit_until_response(self, event_name, **kwargs):
+        """
+        Emit an event by name with arguments passed as keyword args,
+        until the first non-``None`` response is received. This
+        method prevents subsequent handlers from being invoked.
+
+            >>> handler, response = emitter.emit_until_response(
+                'my-event.service.operation', arg1='one', arg2='two')
+
+        :rtype: tuple
+        :return: The first (handler, response) tuple where the response
+                 is not ``None``, otherwise (``None``, ``None``).
+        """
+        responses = self._emit(event_name, kwargs, stop_on_response=True)
+        if responses:
+            return responses[-1]
+        else:
+            return (None, None)
+
+    def _register(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        self._register_section(
+            event_name,
+            handler,
+            unique_id,
+            unique_id_uses_count,
+            section=_MIDDLE,
+        )
+
+    def _register_first(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        self._register_section(
+            event_name,
+            handler,
+            unique_id,
+            unique_id_uses_count,
+            section=_FIRST,
+        )
+
+    def _register_last(
+        self, event_name, handler, unique_id, unique_id_uses_count=False
+    ):
+        self._register_section(
+            event_name, handler, unique_id, unique_id_uses_count, section=_LAST
+        )
+
+    def _register_section(
+        self, event_name, handler, unique_id, unique_id_uses_count, section
+    ):
+        if unique_id is not None:
+            if unique_id in self._unique_id_handlers:
+                # We've already registered a handler using this unique_id
+                # so we don't need to register it again.
+                count = self._unique_id_handlers[unique_id].get('count', None)
+                if unique_id_uses_count:
+                    if not count:
+                        raise ValueError(
+                            f"Initial registration of  unique id {unique_id} was "
+                            "specified to use a counter. Subsequent register "
+                            "calls to unique id must specify use of a counter "
+                            "as well."
+                        )
+                    else:
+                        self._unique_id_handlers[unique_id]['count'] += 1
+                else:
+                    if count:
+                        raise ValueError(
+                            f"Initial registration of unique id {unique_id} was "
+                            "specified to not use a counter. Subsequent "
+                            "register calls to unique id must specify not to "
+                            "use a counter as well."
+                        )
+                return
+            else:
+                # Note that the trie knows nothing about the unique
+                # id.  We track uniqueness in this class via the
+                # _unique_id_handlers.
+                self._handlers.append_item(
+                    event_name, handler, section=section
+                )
+                unique_id_handler_item = {'handler': handler}
+                if unique_id_uses_count:
+                    unique_id_handler_item['count'] = 1
+                self._unique_id_handlers[unique_id] = unique_id_handler_item
+        else:
+            self._handlers.append_item(event_name, handler, section=section)
+        # Super simple caching strategy for now, if we change the registrations
+        # clear the cache.  This has the opportunity for smarter invalidations.
+        self._lookup_cache = {}
+
+    def unregister(
+        self,
+        event_name,
+        handler=None,
+        unique_id=None,
+        unique_id_uses_count=False,
+    ):
+        if unique_id is not None:
+            try:
+                count = self._unique_id_handlers[unique_id].get('count', None)
+            except KeyError:
+                # There's no handler matching that unique_id so we have
+                # nothing to unregister.
+                return
+            if unique_id_uses_count:
+                if count is None:
+                    raise ValueError(
+                        f"Initial registration of unique id {unique_id} was specified to "
+                        "use a counter. Subsequent unregister calls to unique "
+                        "id must specify use of a counter as well."
+                    )
+                elif count == 1:
+                    handler = self._unique_id_handlers.pop(unique_id)[
+                        'handler'
+                    ]
+                else:
+                    self._unique_id_handlers[unique_id]['count'] -= 1
+                    return
+            else:
+                if count:
+                    raise ValueError(
+                        f"Initial registration of unique id {unique_id} was specified "
+                        "to not use a counter. Subsequent unregister calls "
+                        "to unique id must specify not to use a counter as "
+                        "well."
+                    )
+                handler = self._unique_id_handlers.pop(unique_id)['handler']
+        try:
+            self._handlers.remove_item(event_name, handler)
+            self._lookup_cache = {}
+        except ValueError:
+            pass
+
+    def __copy__(self):
+        new_instance = self.__class__()
+        new_state = self.__dict__.copy()
+        new_state['_handlers'] = copy.copy(self._handlers)
+        new_state['_unique_id_handlers'] = copy.copy(self._unique_id_handlers)
+        new_instance.__dict__ = new_state
+        return new_instance
+
+
+class EventAliaser(BaseEventHooks):
+    def __init__(self, event_emitter, event_aliases=None):
+        self._event_aliases = event_aliases
+        if event_aliases is None:
+            self._event_aliases = EVENT_ALIASES
+        self._alias_name_cache = {}
+        self._emitter = event_emitter
+
+    def emit(self, event_name, **kwargs):
+        aliased_event_name = self._alias_event_name(event_name)
+        return self._emitter.emit(aliased_event_name, **kwargs)
+
+    def emit_until_response(self, event_name, **kwargs):
+        aliased_event_name = self._alias_event_name(event_name)
+        return self._emitter.emit_until_response(aliased_event_name, **kwargs)
+
+    def register(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        aliased_event_name = self._alias_event_name(event_name)
+        return self._emitter.register(
+            aliased_event_name, handler, unique_id, unique_id_uses_count
+        )
+
+    def register_first(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        aliased_event_name = self._alias_event_name(event_name)
+        return self._emitter.register_first(
+            aliased_event_name, handler, unique_id, unique_id_uses_count
+        )
+
+    def register_last(
+        self, event_name, handler, unique_id=None, unique_id_uses_count=False
+    ):
+        aliased_event_name = self._alias_event_name(event_name)
+        return self._emitter.register_last(
+            aliased_event_name, handler, unique_id, unique_id_uses_count
+        )
+
+    def unregister(
+        self,
+        event_name,
+        handler=None,
+        unique_id=None,
+        unique_id_uses_count=False,
+    ):
+        aliased_event_name = self._alias_event_name(event_name)
+        return self._emitter.unregister(
+            aliased_event_name, handler, unique_id, unique_id_uses_count
+        )
+
+    def _alias_event_name(self, event_name):
+        if event_name in self._alias_name_cache:
+            return self._alias_name_cache[event_name]
+
+        for old_part, new_part in self._event_aliases.items():
+            # We can't simply do a string replace for everything, otherwise we
+            # might end up translating substrings that we never intended to
+            # translate. When there aren't any dots in the old event name
+            # part, then we can quickly replace the item in the list if it's
+            # there.
+            event_parts = event_name.split('.')
+            if '.' not in old_part:
+                try:
+                    # Theoretically a given event name could have the same part
+                    # repeated, but in practice this doesn't happen
+                    event_parts[event_parts.index(old_part)] = new_part
+                except ValueError:
+                    continue
+
+            # If there's dots in the name, it gets more complicated. Now we
+            # have to replace multiple sections of the original event.
+            elif old_part in event_name:
+                old_parts = old_part.split('.')
+                self._replace_subsection(event_parts, old_parts, new_part)
+            else:
+                continue
+
+            new_name = '.'.join(event_parts)
+            logger.debug(
+                f"Changing event name from {event_name} to {new_name}"
+            )
+            self._alias_name_cache[event_name] = new_name
+            return new_name
+
+        self._alias_name_cache[event_name] = event_name
+        return event_name
+
+    def _replace_subsection(self, sections, old_parts, new_part):
+        for i in range(len(sections)):
+            if (
+                sections[i] == old_parts[0]
+                and sections[i : i + len(old_parts)] == old_parts
+            ):
+                sections[i : i + len(old_parts)] = [new_part]
+                return
+
+    def __copy__(self):
+        return self.__class__(
+            copy.copy(self._emitter), copy.copy(self._event_aliases)
+        )
+
+
+class _PrefixTrie:
+    """Specialized prefix trie that handles wildcards.
+
+    The prefixes in this case are based on dot separated
+    names so 'foo.bar.baz' is::
+
+        foo -> bar -> baz
+
+    Wildcard support just means that having a key such as 'foo.bar.*.baz' will
+    be matched with a call to ``get_items(key='foo.bar.ANYTHING.baz')``.
+
+    You can think of this prefix trie as the equivalent as defaultdict(list),
+    except that it can do prefix searches:
+
+        foo.bar.baz -> A
+        foo.bar -> B
+        foo -> C
+
+    Calling ``get_items('foo.bar.baz')`` will return [A + B + C], from
+    most specific to least specific.
+
+    """
+
+    def __init__(self):
+        # Each dictionary can be though of as a node, where a node
+        # has values associated with the node, and children is a link
+        # to more nodes.  So 'foo.bar' would have a 'foo' node with
+        # a 'bar' node as a child of foo.
+        # {'foo': {'children': {'bar': {...}}}}.
+        self._root = {'chunk': None, 'children': {}, 'values': None}
+
+    def append_item(self, key, value, section=_MIDDLE):
+        """Add an item to a key.
+
+        If a value is already associated with that key, the new
+        value is appended to the list for the key.
+        """
+        key_parts = key.split('.')
+        current = self._root
+        for part in key_parts:
+            if part not in current['children']:
+                new_child = {'chunk': part, 'values': None, 'children': {}}
+                current['children'][part] = new_child
+                current = new_child
+            else:
+                current = current['children'][part]
+        if current['values'] is None:
+            current['values'] = NodeList([], [], [])
+        current['values'][section].append(value)
+
+    def prefix_search(self, key):
+        """Collect all items that are prefixes of key.
+
+        Prefix in this case are delineated by '.' characters so
+        'foo.bar.baz' is a 3 chunk sequence of 3 "prefixes" (
+        "foo", "bar", and "baz").
+
+        """
+        collected = deque()
+        key_parts = key.split('.')
+        current = self._root
+        self._get_items(current, key_parts, collected, 0)
+        return collected
+
+    def _get_items(self, starting_node, key_parts, collected, starting_index):
+        stack = [(starting_node, starting_index)]
+        key_parts_len = len(key_parts)
+        # Traverse down the nodes, where at each level we add the
+        # next part from key_parts as well as the wildcard element '*'.
+        # This means for each node we see we potentially add two more
+        # elements to our stack.
+        while stack:
+            current_node, index = stack.pop()
+            if current_node['values']:
+                # We're using extendleft because we want
+                # the values associated with the node furthest
+                # from the root to come before nodes closer
+                # to the root.  extendleft() also adds its items
+                # in right-left order so .extendleft([1, 2, 3])
+                # will result in final_list = [3, 2, 1], which is
+                # why we reverse the lists.
+                node_list = current_node['values']
+                complete_order = (
+                    node_list.first + node_list.middle + node_list.last
+                )
+                collected.extendleft(reversed(complete_order))
+            if not index == key_parts_len:
+                children = current_node['children']
+                directs = children.get(key_parts[index])
+                wildcard = children.get('*')
+                next_index = index + 1
+                if wildcard is not None:
+                    stack.append((wildcard, next_index))
+                if directs is not None:
+                    stack.append((directs, next_index))
+
+    def remove_item(self, key, value):
+        """Remove an item associated with a key.
+
+        If the value is not associated with the key a ``ValueError``
+        will be raised.  If the key does not exist in the trie, a
+        ``ValueError`` will be raised.
+
+        """
+        key_parts = key.split('.')
+        current = self._root
+        self._remove_item(current, key_parts, value, index=0)
+
+    def _remove_item(self, current_node, key_parts, value, index):
+        if current_node is None:
+            return
+        elif index < len(key_parts):
+            next_node = current_node['children'].get(key_parts[index])
+            if next_node is not None:
+                self._remove_item(next_node, key_parts, value, index + 1)
+                if index == len(key_parts) - 1:
+                    node_list = next_node['values']
+                    if value in node_list.first:
+                        node_list.first.remove(value)
+                    elif value in node_list.middle:
+                        node_list.middle.remove(value)
+                    elif value in node_list.last:
+                        node_list.last.remove(value)
+                if not next_node['children'] and not next_node['values']:
+                    # Then this is a leaf node with no values so
+                    # we can just delete this link from the parent node.
+                    # This makes subsequent search faster in the case
+                    # where a key does not exist.
+                    del current_node['children'][key_parts[index]]
+            else:
+                raise ValueError(f"key is not in trie: {'.'.join(key_parts)}")
+
+    def __copy__(self):
+        # The fact that we're using a nested dict under the covers
+        # is an implementation detail, and the user shouldn't have
+        # to know that they'd normally need a deepcopy so we expose
+        # __copy__ instead of __deepcopy__.
+        new_copy = self.__class__()
+        copied_attrs = self._recursive_copy(self.__dict__)
+        new_copy.__dict__ = copied_attrs
+        return new_copy
+
+    def _recursive_copy(self, node):
+        # We can't use copy.deepcopy because we actually only want to copy
+        # the structure of the trie, not the handlers themselves.
+        # Each node has a chunk, children, and values.
+        copied_node = {}
+        for key, value in node.items():
+            if isinstance(value, NodeList):
+                copied_node[key] = copy.copy(value)
+            elif isinstance(value, dict):
+                copied_node[key] = self._recursive_copy(value)
+            else:
+                copied_node[key] = value
+        return copied_node