automated terminal push
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
257
cdk-env/lib/python3.12/site-packages/boto3/resources/action.py
Normal file
257
cdk-env/lib/python3.12/site-packages/boto3/resources/action.py
Normal file
@@ -0,0 +1,257 @@
|
||||
# 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 botocore import xform_name
|
||||
|
||||
from boto3.docs.docstring import ActionDocstring
|
||||
from boto3.utils import inject_attribute
|
||||
|
||||
from .model import Action
|
||||
from .params import create_request_parameters
|
||||
from .response import RawHandler, ResourceHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceAction:
|
||||
"""
|
||||
A class representing a callable action on a resource, for example
|
||||
``sqs.get_queue_by_name(...)`` or ``s3.Bucket('foo').delete()``.
|
||||
The action may construct parameters from existing resource identifiers
|
||||
and may return either a raw response or a new resource instance.
|
||||
|
||||
: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.
|
||||
|
||||
:type service_context: :py:class:`~boto3.utils.ServiceContext`
|
||||
:param service_context: Context about the AWS service
|
||||
"""
|
||||
|
||||
def __init__(self, action_model, factory=None, service_context=None):
|
||||
self._action_model = action_model
|
||||
|
||||
# In the simplest case we just return the response, but if a
|
||||
# resource is defined, then we must create these before returning.
|
||||
resource_response_model = action_model.resource
|
||||
if resource_response_model:
|
||||
self._response_handler = ResourceHandler(
|
||||
search_path=resource_response_model.path,
|
||||
factory=factory,
|
||||
resource_model=resource_response_model,
|
||||
service_context=service_context,
|
||||
operation_name=action_model.request.operation,
|
||||
)
|
||||
else:
|
||||
self._response_handler = RawHandler(action_model.path)
|
||||
|
||||
def __call__(self, parent, *args, **kwargs):
|
||||
"""
|
||||
Perform the action's request operation after building operation
|
||||
parameters and build any defined resources from the response.
|
||||
|
||||
:type parent: :py:class:`~boto3.resources.base.ServiceResource`
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
:rtype: dict or ServiceResource or list(ServiceResource)
|
||||
:return: The response, either as a raw dict or resource instance(s).
|
||||
"""
|
||||
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, self._action_model.request)
|
||||
params.update(kwargs)
|
||||
|
||||
logger.debug(
|
||||
'Calling %s:%s with %r',
|
||||
parent.meta.service_name,
|
||||
operation_name,
|
||||
params,
|
||||
)
|
||||
|
||||
response = getattr(parent.meta.client, operation_name)(*args, **params)
|
||||
|
||||
logger.debug('Response: %r', response)
|
||||
|
||||
return self._response_handler(parent, params, response)
|
||||
|
||||
|
||||
class BatchAction(ServiceAction):
|
||||
"""
|
||||
An action which operates on a batch of items in a collection, typically
|
||||
a single page of results from the collection's underlying service
|
||||
operation call. For example, this allows you to delete up to 999
|
||||
S3 objects in a single operation rather than calling ``.delete()`` on
|
||||
each one individually.
|
||||
|
||||
: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.
|
||||
|
||||
:type service_context: :py:class:`~boto3.utils.ServiceContext`
|
||||
:param service_context: Context about the AWS service
|
||||
"""
|
||||
|
||||
def __call__(self, parent, *args, **kwargs):
|
||||
"""
|
||||
Perform the batch action's operation on every page of results
|
||||
from the collection.
|
||||
|
||||
:type parent:
|
||||
:py:class:`~boto3.resources.collection.ResourceCollection`
|
||||
:param parent: The collection iterator to which this action
|
||||
is attached.
|
||||
:rtype: list(dict)
|
||||
:return: A list of low-level response dicts from each call.
|
||||
"""
|
||||
service_name = None
|
||||
client = None
|
||||
responses = []
|
||||
operation_name = xform_name(self._action_model.request.operation)
|
||||
|
||||
# Unlike the simple action above, a batch action must operate
|
||||
# on batches (or pages) of items. So we get each page, construct
|
||||
# the necessary parameters and call the batch operation.
|
||||
for page in parent.pages():
|
||||
params = {}
|
||||
for index, resource in enumerate(page):
|
||||
# There is no public interface to get a service name
|
||||
# or low-level client from a collection, so we get
|
||||
# these from the first resource in the collection.
|
||||
if service_name is None:
|
||||
service_name = resource.meta.service_name
|
||||
if client is None:
|
||||
client = resource.meta.client
|
||||
|
||||
create_request_parameters(
|
||||
resource,
|
||||
self._action_model.request,
|
||||
params=params,
|
||||
index=index,
|
||||
)
|
||||
|
||||
if not params:
|
||||
# There are no items, no need to make a call.
|
||||
break
|
||||
|
||||
params.update(kwargs)
|
||||
|
||||
logger.debug(
|
||||
'Calling %s:%s with %r', service_name, operation_name, params
|
||||
)
|
||||
|
||||
response = getattr(client, operation_name)(*args, **params)
|
||||
|
||||
logger.debug('Response: %r', response)
|
||||
|
||||
responses.append(self._response_handler(parent, params, response))
|
||||
|
||||
return responses
|
||||
|
||||
|
||||
class WaiterAction:
|
||||
"""
|
||||
A class representing a callable waiter action on a resource, for example
|
||||
``s3.Bucket('foo').wait_until_bucket_exists()``.
|
||||
The waiter action may construct parameters from existing resource
|
||||
identifiers.
|
||||
|
||||
:type waiter_model: :py:class`~boto3.resources.model.Waiter`
|
||||
:param waiter_model: The action waiter.
|
||||
:type waiter_resource_name: string
|
||||
:param waiter_resource_name: The name of the waiter action for the
|
||||
resource. It usually begins with a
|
||||
``wait_until_``
|
||||
"""
|
||||
|
||||
def __init__(self, waiter_model, waiter_resource_name):
|
||||
self._waiter_model = waiter_model
|
||||
self._waiter_resource_name = waiter_resource_name
|
||||
|
||||
def __call__(self, parent, *args, **kwargs):
|
||||
"""
|
||||
Perform the wait operation after building operation
|
||||
parameters.
|
||||
|
||||
:type parent: :py:class:`~boto3.resources.base.ServiceResource`
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
"""
|
||||
client_waiter_name = xform_name(self._waiter_model.waiter_name)
|
||||
|
||||
# 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, self._waiter_model)
|
||||
params.update(kwargs)
|
||||
|
||||
logger.debug(
|
||||
'Calling %s:%s with %r',
|
||||
parent.meta.service_name,
|
||||
self._waiter_resource_name,
|
||||
params,
|
||||
)
|
||||
|
||||
client = parent.meta.client
|
||||
waiter = client.get_waiter(client_waiter_name)
|
||||
response = waiter.wait(**params)
|
||||
|
||||
logger.debug('Response: %r', response)
|
||||
|
||||
|
||||
class CustomModeledAction:
|
||||
"""A custom, modeled action to inject into a resource."""
|
||||
|
||||
def __init__(self, action_name, action_model, function, event_emitter):
|
||||
"""
|
||||
:type action_name: str
|
||||
:param action_name: The name of the action to inject, e.g.
|
||||
'delete_tags'
|
||||
|
||||
:type action_model: dict
|
||||
:param action_model: A JSON definition of the action, as if it were
|
||||
part of the resource model.
|
||||
|
||||
:type function: function
|
||||
:param function: The function to perform when the action is called.
|
||||
The first argument should be 'self', which will be the resource
|
||||
the function is to be called on.
|
||||
|
||||
:type event_emitter: :py:class:`botocore.hooks.BaseEventHooks`
|
||||
:param event_emitter: The session event emitter.
|
||||
"""
|
||||
self.name = action_name
|
||||
self.model = action_model
|
||||
self.function = function
|
||||
self.emitter = event_emitter
|
||||
|
||||
def inject(self, class_attributes, service_context, event_name, **kwargs):
|
||||
resource_name = event_name.rsplit(".")[-1]
|
||||
action = Action(self.name, self.model, {})
|
||||
self.function.__name__ = self.name
|
||||
self.function.__doc__ = ActionDocstring(
|
||||
resource_name=resource_name,
|
||||
event_emitter=self.emitter,
|
||||
action_model=action,
|
||||
service_model=service_context.service_model,
|
||||
include_signature=False,
|
||||
)
|
||||
inject_attribute(class_attributes, self.name, self.function)
|
||||
153
cdk-env/lib/python3.12/site-packages/boto3/resources/base.py
Normal file
153
cdk-env/lib/python3.12/site-packages/boto3/resources/base.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# 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
|
||||
|
||||
import boto3
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceMeta:
|
||||
"""
|
||||
An object containing metadata about a resource.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
service_name,
|
||||
identifiers=None,
|
||||
client=None,
|
||||
data=None,
|
||||
resource_model=None,
|
||||
):
|
||||
#: (``string``) The service name, e.g. 's3'
|
||||
self.service_name = service_name
|
||||
|
||||
if identifiers is None:
|
||||
identifiers = []
|
||||
#: (``list``) List of identifier names
|
||||
self.identifiers = identifiers
|
||||
|
||||
#: (:py:class:`~botocore.client.BaseClient`) Low-level Botocore client
|
||||
self.client = client
|
||||
#: (``dict``) Loaded resource data attributes
|
||||
self.data = data
|
||||
|
||||
# The resource model for that resource
|
||||
self.resource_model = resource_model
|
||||
|
||||
def __repr__(self):
|
||||
return f'ResourceMeta(\'{self.service_name}\', identifiers={self.identifiers})'
|
||||
|
||||
def __eq__(self, other):
|
||||
# Two metas are equal if their components are all equal
|
||||
if other.__class__.__name__ != self.__class__.__name__:
|
||||
return False
|
||||
|
||||
return self.__dict__ == other.__dict__
|
||||
|
||||
def copy(self):
|
||||
"""
|
||||
Create a copy of this metadata object.
|
||||
"""
|
||||
params = self.__dict__.copy()
|
||||
service_name = params.pop('service_name')
|
||||
return ResourceMeta(service_name, **params)
|
||||
|
||||
|
||||
class ServiceResource:
|
||||
"""
|
||||
A base class for resources.
|
||||
|
||||
:type client: botocore.client
|
||||
:param client: A low-level Botocore client instance
|
||||
"""
|
||||
|
||||
meta = None
|
||||
"""
|
||||
Stores metadata about this resource instance, such as the
|
||||
``service_name``, the low-level ``client`` and any cached ``data``
|
||||
from when the instance was hydrated. For example::
|
||||
|
||||
# Get a low-level client from a resource instance
|
||||
client = resource.meta.client
|
||||
response = client.operation(Param='foo')
|
||||
|
||||
# Print the resource instance's service short name
|
||||
print(resource.meta.service_name)
|
||||
|
||||
See :py:class:`ResourceMeta` for more information.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Always work on a copy of meta, otherwise we would affect other
|
||||
# instances of the same subclass.
|
||||
self.meta = self.meta.copy()
|
||||
|
||||
# Create a default client if none was passed
|
||||
if kwargs.get('client') is not None:
|
||||
self.meta.client = kwargs.get('client')
|
||||
else:
|
||||
self.meta.client = boto3.client(self.meta.service_name)
|
||||
|
||||
# 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)
|
||||
|
||||
# Allow setting identifiers via keyword arguments. Here we need
|
||||
# extra logic to ignore other keyword arguments like ``client``.
|
||||
for name, value in kwargs.items():
|
||||
if name == 'client':
|
||||
continue
|
||||
|
||||
if name not in self.meta.identifiers:
|
||||
raise ValueError(f'Unknown keyword argument: {name}')
|
||||
|
||||
setattr(self, '_' + name, value)
|
||||
|
||||
# Validate that all identifiers have been set.
|
||||
for identifier in self.meta.identifiers:
|
||||
if getattr(self, identifier) is None:
|
||||
raise ValueError(f'Required parameter {identifier} not set')
|
||||
|
||||
def __repr__(self):
|
||||
identifiers = []
|
||||
for identifier in self.meta.identifiers:
|
||||
identifiers.append(
|
||||
f'{identifier}={repr(getattr(self, identifier))}'
|
||||
)
|
||||
return "{}({})".format(
|
||||
self.__class__.__name__,
|
||||
', '.join(identifiers),
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
# Should be instances of the same resource class
|
||||
if other.__class__.__name__ != self.__class__.__name__:
|
||||
return False
|
||||
|
||||
# Each of the identifiers should have the same value in both
|
||||
# instances, e.g. two buckets need the same name to be equal.
|
||||
for identifier in self.meta.identifiers:
|
||||
if getattr(self, identifier) != getattr(other, identifier):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def __hash__(self):
|
||||
identifiers = []
|
||||
for identifier in self.meta.identifiers:
|
||||
identifiers.append(getattr(self, identifier))
|
||||
return hash((self.__class__.__name__, tuple(identifiers)))
|
||||
@@ -0,0 +1,566 @@
|
||||
# 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 copy
|
||||
import logging
|
||||
|
||||
from botocore import xform_name
|
||||
from botocore.utils import merge_dicts
|
||||
|
||||
from ..docs import docstring
|
||||
from .action import BatchAction
|
||||
from .params import create_request_parameters
|
||||
from .response import ResourceHandler
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourceCollection:
|
||||
"""
|
||||
Represents a collection of resources, which can be iterated through,
|
||||
optionally with filtering. Collections automatically handle pagination
|
||||
for you.
|
||||
|
||||
See :ref:`guide_collections` for a high-level overview of collections,
|
||||
including when remote service requests are performed.
|
||||
|
||||
: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, model, parent, handler, **kwargs):
|
||||
self._model = model
|
||||
self._parent = parent
|
||||
self._py_operation_name = xform_name(model.request.operation)
|
||||
self._handler = handler
|
||||
self._params = copy.deepcopy(kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return '{}({}, {})'.format(
|
||||
self.__class__.__name__,
|
||||
self._parent,
|
||||
f'{self._parent.meta.service_name}.{self._model.resource.type}',
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
A generator which yields resource instances after doing the
|
||||
appropriate service operation calls and handling any pagination
|
||||
on your behalf.
|
||||
|
||||
Page size, item limit, and filter parameters are applied
|
||||
if they have previously been set.
|
||||
|
||||
>>> bucket = s3.Bucket('boto3')
|
||||
>>> for obj in bucket.objects.all():
|
||||
... print(obj.key)
|
||||
'key1'
|
||||
'key2'
|
||||
|
||||
"""
|
||||
limit = self._params.get('limit', None)
|
||||
|
||||
count = 0
|
||||
for page in self.pages():
|
||||
for item in page:
|
||||
yield item
|
||||
|
||||
# If the limit is set and has been reached, then
|
||||
# we stop processing items here.
|
||||
count += 1
|
||||
if limit is not None and count >= limit:
|
||||
return
|
||||
|
||||
def _clone(self, **kwargs):
|
||||
"""
|
||||
Create a clone of this collection. This is used by the methods
|
||||
below to provide a chainable interface that returns copies
|
||||
rather than the original. This allows things like:
|
||||
|
||||
>>> base = collection.filter(Param1=1)
|
||||
>>> query1 = base.filter(Param2=2)
|
||||
>>> query2 = base.filter(Param3=3)
|
||||
>>> query1.params
|
||||
{'Param1': 1, 'Param2': 2}
|
||||
>>> query2.params
|
||||
{'Param1': 1, 'Param3': 3}
|
||||
|
||||
:rtype: :py:class:`ResourceCollection`
|
||||
:return: A clone of this resource collection
|
||||
"""
|
||||
params = copy.deepcopy(self._params)
|
||||
merge_dicts(params, kwargs, append_lists=True)
|
||||
clone = self.__class__(
|
||||
self._model, self._parent, self._handler, **params
|
||||
)
|
||||
return clone
|
||||
|
||||
def pages(self):
|
||||
"""
|
||||
A generator which yields pages of resource instances after
|
||||
doing the appropriate service operation calls and handling
|
||||
any pagination on your behalf. Non-paginated calls will
|
||||
return a single page of items.
|
||||
|
||||
Page size, item limit, and filter parameters are applied
|
||||
if they have previously been set.
|
||||
|
||||
>>> bucket = s3.Bucket('boto3')
|
||||
>>> for page in bucket.objects.pages():
|
||||
... for obj in page:
|
||||
... print(obj.key)
|
||||
'key1'
|
||||
'key2'
|
||||
|
||||
:rtype: list(:py:class:`~boto3.resources.base.ServiceResource`)
|
||||
:return: List of resource instances
|
||||
"""
|
||||
client = self._parent.meta.client
|
||||
cleaned_params = self._params.copy()
|
||||
limit = cleaned_params.pop('limit', None)
|
||||
page_size = cleaned_params.pop('page_size', None)
|
||||
params = create_request_parameters(self._parent, self._model.request)
|
||||
merge_dicts(params, cleaned_params, append_lists=True)
|
||||
|
||||
# Is this a paginated operation? If so, we need to get an
|
||||
# iterator for the various pages. If not, then we simply
|
||||
# call the operation and return the result as a single
|
||||
# page in a list. For non-paginated results, we just ignore
|
||||
# the page size parameter.
|
||||
if client.can_paginate(self._py_operation_name):
|
||||
logger.debug(
|
||||
'Calling paginated %s:%s with %r',
|
||||
self._parent.meta.service_name,
|
||||
self._py_operation_name,
|
||||
params,
|
||||
)
|
||||
paginator = client.get_paginator(self._py_operation_name)
|
||||
pages = paginator.paginate(
|
||||
PaginationConfig={'MaxItems': limit, 'PageSize': page_size},
|
||||
**params,
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
'Calling %s:%s with %r',
|
||||
self._parent.meta.service_name,
|
||||
self._py_operation_name,
|
||||
params,
|
||||
)
|
||||
pages = [getattr(client, self._py_operation_name)(**params)]
|
||||
|
||||
# Now that we have a page iterator or single page of results
|
||||
# we start processing and yielding individual items.
|
||||
count = 0
|
||||
for page in pages:
|
||||
page_items = []
|
||||
for item in self._handler(self._parent, params, page):
|
||||
page_items.append(item)
|
||||
|
||||
# If the limit is set and has been reached, then
|
||||
# we stop processing items here.
|
||||
count += 1
|
||||
if limit is not None and count >= limit:
|
||||
break
|
||||
|
||||
yield page_items
|
||||
|
||||
# Stop reading pages if we've reached out limit
|
||||
if limit is not None and count >= limit:
|
||||
break
|
||||
|
||||
def all(self):
|
||||
"""
|
||||
Get all items from the collection, optionally with a custom
|
||||
page size and item count limit.
|
||||
|
||||
This method returns an iterable generator which yields
|
||||
individual resource instances. Example use::
|
||||
|
||||
# Iterate through items
|
||||
>>> for queue in sqs.queues.all():
|
||||
... print(queue.url)
|
||||
'https://url1'
|
||||
'https://url2'
|
||||
|
||||
# Convert to list
|
||||
>>> queues = list(sqs.queues.all())
|
||||
>>> len(queues)
|
||||
2
|
||||
"""
|
||||
return self._clone()
|
||||
|
||||
def filter(self, **kwargs):
|
||||
"""
|
||||
Get items from the collection, passing keyword arguments along
|
||||
as parameters to the underlying service operation, which are
|
||||
typically used to filter the results.
|
||||
|
||||
This method returns an iterable generator which yields
|
||||
individual resource instances. Example use::
|
||||
|
||||
# Iterate through items
|
||||
>>> for queue in sqs.queues.filter(Param='foo'):
|
||||
... print(queue.url)
|
||||
'https://url1'
|
||||
'https://url2'
|
||||
|
||||
# Convert to list
|
||||
>>> queues = list(sqs.queues.filter(Param='foo'))
|
||||
>>> len(queues)
|
||||
2
|
||||
|
||||
:rtype: :py:class:`ResourceCollection`
|
||||
"""
|
||||
return self._clone(**kwargs)
|
||||
|
||||
def limit(self, count):
|
||||
"""
|
||||
Return at most this many resources.
|
||||
|
||||
>>> for bucket in s3.buckets.limit(5):
|
||||
... print(bucket.name)
|
||||
'bucket1'
|
||||
'bucket2'
|
||||
'bucket3'
|
||||
'bucket4'
|
||||
'bucket5'
|
||||
|
||||
:type count: int
|
||||
:param count: Return no more than this many items
|
||||
:rtype: :py:class:`ResourceCollection`
|
||||
"""
|
||||
return self._clone(limit=count)
|
||||
|
||||
def page_size(self, count):
|
||||
"""
|
||||
Fetch at most this many resources per service request.
|
||||
|
||||
>>> for obj in s3.Bucket('boto3').objects.page_size(100):
|
||||
... print(obj.key)
|
||||
|
||||
:type count: int
|
||||
:param count: Fetch this many items per request
|
||||
:rtype: :py:class:`ResourceCollection`
|
||||
"""
|
||||
return self._clone(page_size=count)
|
||||
|
||||
|
||||
class CollectionManager:
|
||||
"""
|
||||
A collection manager provides access to resource collection instances,
|
||||
which can be iterated and filtered. The manager exposes some
|
||||
convenience functions that are also found on resource collections,
|
||||
such as :py:meth:`~ResourceCollection.all` and
|
||||
:py:meth:`~ResourceCollection.filter`.
|
||||
|
||||
Get all items::
|
||||
|
||||
>>> for bucket in s3.buckets.all():
|
||||
... print(bucket.name)
|
||||
|
||||
Get only some items via filtering::
|
||||
|
||||
>>> for queue in sqs.queues.filter(QueueNamePrefix='AWS'):
|
||||
... print(queue.url)
|
||||
|
||||
Get whole pages of items:
|
||||
|
||||
>>> for page in s3.Bucket('boto3').objects.pages():
|
||||
... for obj in page:
|
||||
... print(obj.key)
|
||||
|
||||
A collection manager is not iterable. You **must** call one of the
|
||||
methods that return a :py:class:`ResourceCollection` before trying
|
||||
to iterate, slice, or convert to a list.
|
||||
|
||||
See the :ref:`guide_collections` guide for a high-level overview
|
||||
of collections, including when remote service requests are performed.
|
||||
|
||||
:type collection_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`
|
||||
:param factory: The resource factory to create new resources
|
||||
|
||||
:type service_context: :py:class:`~boto3.utils.ServiceContext`
|
||||
:param service_context: Context about the AWS service
|
||||
"""
|
||||
|
||||
# The class to use when creating an iterator
|
||||
_collection_cls = ResourceCollection
|
||||
|
||||
def __init__(self, collection_model, parent, factory, service_context):
|
||||
self._model = collection_model
|
||||
operation_name = self._model.request.operation
|
||||
self._parent = parent
|
||||
|
||||
search_path = collection_model.resource.path
|
||||
self._handler = ResourceHandler(
|
||||
search_path=search_path,
|
||||
factory=factory,
|
||||
resource_model=collection_model.resource,
|
||||
service_context=service_context,
|
||||
operation_name=operation_name,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '{}({}, {})'.format(
|
||||
self.__class__.__name__,
|
||||
self._parent,
|
||||
f'{self._parent.meta.service_name}.{self._model.resource.type}',
|
||||
)
|
||||
|
||||
def iterator(self, **kwargs):
|
||||
"""
|
||||
Get a resource collection iterator from this manager.
|
||||
|
||||
:rtype: :py:class:`ResourceCollection`
|
||||
:return: An iterable representing the collection of resources
|
||||
"""
|
||||
return self._collection_cls(
|
||||
self._model, self._parent, self._handler, **kwargs
|
||||
)
|
||||
|
||||
# Set up some methods to proxy ResourceCollection methods
|
||||
def all(self):
|
||||
return self.iterator()
|
||||
|
||||
all.__doc__ = ResourceCollection.all.__doc__
|
||||
|
||||
def filter(self, **kwargs):
|
||||
return self.iterator(**kwargs)
|
||||
|
||||
filter.__doc__ = ResourceCollection.filter.__doc__
|
||||
|
||||
def limit(self, count):
|
||||
return self.iterator(limit=count)
|
||||
|
||||
limit.__doc__ = ResourceCollection.limit.__doc__
|
||||
|
||||
def page_size(self, count):
|
||||
return self.iterator(page_size=count)
|
||||
|
||||
page_size.__doc__ = ResourceCollection.page_size.__doc__
|
||||
|
||||
def pages(self):
|
||||
return self.iterator().pages()
|
||||
|
||||
pages.__doc__ = ResourceCollection.pages.__doc__
|
||||
|
||||
|
||||
class CollectionFactory:
|
||||
"""
|
||||
A factory to create new
|
||||
:py:class:`CollectionManager` and :py:class:`ResourceCollection`
|
||||
subclasses from a :py:class:`~boto3.resources.model.Collection`
|
||||
model. These subclasses include methods to perform batch operations.
|
||||
"""
|
||||
|
||||
def load_from_definition(
|
||||
self, resource_name, collection_model, service_context, event_emitter
|
||||
):
|
||||
"""
|
||||
Loads a collection from a model, creating a new
|
||||
:py:class:`CollectionManager` subclass
|
||||
with the correct properties and methods, named based on the service
|
||||
and resource name, e.g. ec2.InstanceCollectionManager. It also
|
||||
creates a new :py:class:`ResourceCollection` subclass which is used
|
||||
by the new manager class.
|
||||
|
||||
:type resource_name: string
|
||||
:param resource_name: Name of the resource to look up. For services,
|
||||
this should match the ``service_name``.
|
||||
|
||||
:type service_context: :py:class:`~boto3.utils.ServiceContext`
|
||||
:param service_context: Context about the AWS service
|
||||
|
||||
:type event_emitter: :py:class:`~botocore.hooks.HierarchialEmitter`
|
||||
:param event_emitter: An event emitter
|
||||
|
||||
:rtype: Subclass of :py:class:`CollectionManager`
|
||||
:return: The collection class.
|
||||
"""
|
||||
attrs = {}
|
||||
collection_name = collection_model.name
|
||||
|
||||
# Create the batch actions for a collection
|
||||
self._load_batch_actions(
|
||||
attrs,
|
||||
resource_name,
|
||||
collection_model,
|
||||
service_context.service_model,
|
||||
event_emitter,
|
||||
)
|
||||
# Add the documentation to the collection class's methods
|
||||
self._load_documented_collection_methods(
|
||||
attrs=attrs,
|
||||
resource_name=resource_name,
|
||||
collection_model=collection_model,
|
||||
service_model=service_context.service_model,
|
||||
event_emitter=event_emitter,
|
||||
base_class=ResourceCollection,
|
||||
)
|
||||
|
||||
if service_context.service_name == resource_name:
|
||||
cls_name = (
|
||||
f'{service_context.service_name}.{collection_name}Collection'
|
||||
)
|
||||
else:
|
||||
cls_name = f'{service_context.service_name}.{resource_name}.{collection_name}Collection'
|
||||
|
||||
collection_cls = type(str(cls_name), (ResourceCollection,), attrs)
|
||||
|
||||
# Add the documentation to the collection manager's methods
|
||||
self._load_documented_collection_methods(
|
||||
attrs=attrs,
|
||||
resource_name=resource_name,
|
||||
collection_model=collection_model,
|
||||
service_model=service_context.service_model,
|
||||
event_emitter=event_emitter,
|
||||
base_class=CollectionManager,
|
||||
)
|
||||
attrs['_collection_cls'] = collection_cls
|
||||
cls_name += 'Manager'
|
||||
|
||||
return type(str(cls_name), (CollectionManager,), attrs)
|
||||
|
||||
def _load_batch_actions(
|
||||
self,
|
||||
attrs,
|
||||
resource_name,
|
||||
collection_model,
|
||||
service_model,
|
||||
event_emitter,
|
||||
):
|
||||
"""
|
||||
Batch actions on the collection become methods on both
|
||||
the collection manager and iterators.
|
||||
"""
|
||||
for action_model in collection_model.batch_actions:
|
||||
snake_cased = xform_name(action_model.name)
|
||||
attrs[snake_cased] = self._create_batch_action(
|
||||
resource_name,
|
||||
snake_cased,
|
||||
action_model,
|
||||
collection_model,
|
||||
service_model,
|
||||
event_emitter,
|
||||
)
|
||||
|
||||
def _load_documented_collection_methods(
|
||||
factory_self,
|
||||
attrs,
|
||||
resource_name,
|
||||
collection_model,
|
||||
service_model,
|
||||
event_emitter,
|
||||
base_class,
|
||||
):
|
||||
# The base class already has these methods defined. However
|
||||
# the docstrings are generic and not based for a particular service
|
||||
# or resource. So we override these methods by proxying to the
|
||||
# base class's builtin method and adding a docstring
|
||||
# that pertains to the resource.
|
||||
|
||||
# A collection's all() method.
|
||||
def all(self):
|
||||
return base_class.all(self)
|
||||
|
||||
all.__doc__ = docstring.CollectionMethodDocstring(
|
||||
resource_name=resource_name,
|
||||
action_name='all',
|
||||
event_emitter=event_emitter,
|
||||
collection_model=collection_model,
|
||||
service_model=service_model,
|
||||
include_signature=False,
|
||||
)
|
||||
attrs['all'] = all
|
||||
|
||||
# The collection's filter() method.
|
||||
def filter(self, **kwargs):
|
||||
return base_class.filter(self, **kwargs)
|
||||
|
||||
filter.__doc__ = docstring.CollectionMethodDocstring(
|
||||
resource_name=resource_name,
|
||||
action_name='filter',
|
||||
event_emitter=event_emitter,
|
||||
collection_model=collection_model,
|
||||
service_model=service_model,
|
||||
include_signature=False,
|
||||
)
|
||||
attrs['filter'] = filter
|
||||
|
||||
# The collection's limit method.
|
||||
def limit(self, count):
|
||||
return base_class.limit(self, count)
|
||||
|
||||
limit.__doc__ = docstring.CollectionMethodDocstring(
|
||||
resource_name=resource_name,
|
||||
action_name='limit',
|
||||
event_emitter=event_emitter,
|
||||
collection_model=collection_model,
|
||||
service_model=service_model,
|
||||
include_signature=False,
|
||||
)
|
||||
attrs['limit'] = limit
|
||||
|
||||
# The collection's page_size method.
|
||||
def page_size(self, count):
|
||||
return base_class.page_size(self, count)
|
||||
|
||||
page_size.__doc__ = docstring.CollectionMethodDocstring(
|
||||
resource_name=resource_name,
|
||||
action_name='page_size',
|
||||
event_emitter=event_emitter,
|
||||
collection_model=collection_model,
|
||||
service_model=service_model,
|
||||
include_signature=False,
|
||||
)
|
||||
attrs['page_size'] = page_size
|
||||
|
||||
def _create_batch_action(
|
||||
factory_self,
|
||||
resource_name,
|
||||
snake_cased,
|
||||
action_model,
|
||||
collection_model,
|
||||
service_model,
|
||||
event_emitter,
|
||||
):
|
||||
"""
|
||||
Creates a new method which makes a batch operation request
|
||||
to the underlying service API.
|
||||
"""
|
||||
action = BatchAction(action_model)
|
||||
|
||||
def batch_action(self, *args, **kwargs):
|
||||
return action(self, *args, **kwargs)
|
||||
|
||||
batch_action.__name__ = str(snake_cased)
|
||||
batch_action.__doc__ = docstring.BatchActionDocstring(
|
||||
resource_name=resource_name,
|
||||
event_emitter=event_emitter,
|
||||
batch_action_model=action_model,
|
||||
service_model=service_model,
|
||||
collection_model=collection_model,
|
||||
include_signature=False,
|
||||
)
|
||||
return batch_action
|
||||
601
cdk-env/lib/python3.12/site-packages/boto3/resources/factory.py
Normal file
601
cdk-env/lib/python3.12/site-packages/boto3/resources/factory.py
Normal file
@@ -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
|
||||
630
cdk-env/lib/python3.12/site-packages/boto3/resources/model.py
Normal file
630
cdk-env/lib/python3.12/site-packages/boto3/resources/model.py
Normal file
@@ -0,0 +1,630 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
The models defined in this file represent the resource JSON description
|
||||
format and provide a layer of abstraction from the raw JSON. The advantages
|
||||
of this are:
|
||||
|
||||
* Pythonic interface (e.g. ``action.request.operation``)
|
||||
* Consumers need not change for minor JSON changes (e.g. renamed field)
|
||||
|
||||
These models are used both by the resource factory to generate resource
|
||||
classes as well as by the documentation generator.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from botocore import xform_name
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Identifier:
|
||||
"""
|
||||
A resource identifier, given by its name.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the identifier
|
||||
"""
|
||||
|
||||
def __init__(self, name, member_name=None):
|
||||
#: (``string``) The name of the identifier
|
||||
self.name = name
|
||||
self.member_name = member_name
|
||||
|
||||
|
||||
class Action:
|
||||
"""
|
||||
A service operation action.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the action
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
:type resource_defs: dict
|
||||
:param resource_defs: All resources defined in the service
|
||||
"""
|
||||
|
||||
def __init__(self, name, definition, resource_defs):
|
||||
self._definition = definition
|
||||
|
||||
#: (``string``) The name of the action
|
||||
self.name = name
|
||||
#: (:py:class:`Request`) This action's request or ``None``
|
||||
self.request = None
|
||||
if 'request' in definition:
|
||||
self.request = Request(definition.get('request', {}))
|
||||
#: (:py:class:`ResponseResource`) This action's resource or ``None``
|
||||
self.resource = None
|
||||
if 'resource' in definition:
|
||||
self.resource = ResponseResource(
|
||||
definition.get('resource', {}), resource_defs
|
||||
)
|
||||
#: (``string``) The JMESPath search path or ``None``
|
||||
self.path = definition.get('path')
|
||||
|
||||
|
||||
class DefinitionWithParams:
|
||||
"""
|
||||
An item which has parameters exposed via the ``params`` property.
|
||||
A request has an operation and parameters, while a waiter has
|
||||
a name, a low-level waiter name and parameters.
|
||||
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
"""
|
||||
|
||||
def __init__(self, definition):
|
||||
self._definition = definition
|
||||
|
||||
@property
|
||||
def params(self):
|
||||
"""
|
||||
Get a list of auto-filled parameters for this request.
|
||||
|
||||
:type: list(:py:class:`Parameter`)
|
||||
"""
|
||||
params = []
|
||||
|
||||
for item in self._definition.get('params', []):
|
||||
params.append(Parameter(**item))
|
||||
|
||||
return params
|
||||
|
||||
|
||||
class Parameter:
|
||||
"""
|
||||
An auto-filled parameter which has a source and target. For example,
|
||||
the ``QueueUrl`` may be auto-filled from a resource's ``url`` identifier
|
||||
when making calls to ``queue.receive_messages``.
|
||||
|
||||
:type target: string
|
||||
:param target: The destination parameter name, e.g. ``QueueUrl``
|
||||
:type source_type: string
|
||||
:param source_type: Where the source is defined.
|
||||
:type source: string
|
||||
:param source: The source name, e.g. ``Url``
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, target, source, name=None, path=None, value=None, **kwargs
|
||||
):
|
||||
#: (``string``) The destination parameter name
|
||||
self.target = target
|
||||
#: (``string``) Where the source is defined
|
||||
self.source = source
|
||||
#: (``string``) The name of the source, if given
|
||||
self.name = name
|
||||
#: (``string``) The JMESPath query of the source
|
||||
self.path = path
|
||||
#: (``string|int|float|bool``) The source constant value
|
||||
self.value = value
|
||||
|
||||
# Complain if we encounter any unknown values.
|
||||
if kwargs:
|
||||
logger.warning('Unknown parameter options found: %s', kwargs)
|
||||
|
||||
|
||||
class Request(DefinitionWithParams):
|
||||
"""
|
||||
A service operation action request.
|
||||
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
"""
|
||||
|
||||
def __init__(self, definition):
|
||||
super().__init__(definition)
|
||||
|
||||
#: (``string``) The name of the low-level service operation
|
||||
self.operation = definition.get('operation')
|
||||
|
||||
|
||||
class Waiter(DefinitionWithParams):
|
||||
"""
|
||||
An event waiter specification.
|
||||
|
||||
:type name: string
|
||||
:param name: Name of the waiter
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
"""
|
||||
|
||||
PREFIX = 'WaitUntil'
|
||||
|
||||
def __init__(self, name, definition):
|
||||
super().__init__(definition)
|
||||
|
||||
#: (``string``) The name of this waiter
|
||||
self.name = name
|
||||
|
||||
#: (``string``) The name of the underlying event waiter
|
||||
self.waiter_name = definition.get('waiterName')
|
||||
|
||||
|
||||
class ResponseResource:
|
||||
"""
|
||||
A resource response to create after performing an action.
|
||||
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
:type resource_defs: dict
|
||||
:param resource_defs: All resources defined in the service
|
||||
"""
|
||||
|
||||
def __init__(self, definition, resource_defs):
|
||||
self._definition = definition
|
||||
self._resource_defs = resource_defs
|
||||
|
||||
#: (``string``) The name of the response resource type
|
||||
self.type = definition.get('type')
|
||||
|
||||
#: (``string``) The JMESPath search query or ``None``
|
||||
self.path = definition.get('path')
|
||||
|
||||
@property
|
||||
def identifiers(self):
|
||||
"""
|
||||
A list of resource identifiers.
|
||||
|
||||
:type: list(:py:class:`Identifier`)
|
||||
"""
|
||||
identifiers = []
|
||||
|
||||
for item in self._definition.get('identifiers', []):
|
||||
identifiers.append(Parameter(**item))
|
||||
|
||||
return identifiers
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
"""
|
||||
Get the resource model for the response resource.
|
||||
|
||||
:type: :py:class:`ResourceModel`
|
||||
"""
|
||||
return ResourceModel(
|
||||
self.type, self._resource_defs[self.type], self._resource_defs
|
||||
)
|
||||
|
||||
|
||||
class Collection(Action):
|
||||
"""
|
||||
A group of resources. See :py:class:`Action`.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of the collection
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
:type resource_defs: dict
|
||||
:param resource_defs: All resources defined in the service
|
||||
"""
|
||||
|
||||
@property
|
||||
def batch_actions(self):
|
||||
"""
|
||||
Get a list of batch actions supported by the resource type
|
||||
contained in this action. This is a shortcut for accessing
|
||||
the same information through the resource model.
|
||||
|
||||
:rtype: list(:py:class:`Action`)
|
||||
"""
|
||||
return self.resource.model.batch_actions
|
||||
|
||||
|
||||
class ResourceModel:
|
||||
"""
|
||||
A model representing a resource, defined via a JSON description
|
||||
format. A resource has identifiers, attributes, actions,
|
||||
sub-resources, references and collections. For more information
|
||||
on resources, see :ref:`guide_resources`.
|
||||
|
||||
:type name: string
|
||||
:param name: The name of this resource, e.g. ``sqs`` or ``Queue``
|
||||
:type definition: dict
|
||||
:param definition: The JSON definition
|
||||
:type resource_defs: dict
|
||||
:param resource_defs: All resources defined in the service
|
||||
"""
|
||||
|
||||
def __init__(self, name, definition, resource_defs):
|
||||
self._definition = definition
|
||||
self._resource_defs = resource_defs
|
||||
self._renamed = {}
|
||||
|
||||
#: (``string``) The name of this resource
|
||||
self.name = name
|
||||
#: (``string``) The service shape name for this resource or ``None``
|
||||
self.shape = definition.get('shape')
|
||||
|
||||
def load_rename_map(self, shape=None):
|
||||
"""
|
||||
Load a name translation map given a shape. This will set
|
||||
up renamed values for any collisions, e.g. if the shape,
|
||||
an action, and a subresource all are all named ``foo``
|
||||
then the resource will have an action ``foo``, a subresource
|
||||
named ``Foo`` and a property named ``foo_attribute``.
|
||||
This is the order of precedence, from most important to
|
||||
least important:
|
||||
|
||||
* Load action (resource.load)
|
||||
* Identifiers
|
||||
* Actions
|
||||
* Subresources
|
||||
* References
|
||||
* Collections
|
||||
* Waiters
|
||||
* Attributes (shape members)
|
||||
|
||||
Batch actions are only exposed on collections, so do not
|
||||
get modified here. Subresources use upper camel casing, so
|
||||
are unlikely to collide with anything but other subresources.
|
||||
|
||||
Creates a structure like this::
|
||||
|
||||
renames = {
|
||||
('action', 'id'): 'id_action',
|
||||
('collection', 'id'): 'id_collection',
|
||||
('attribute', 'id'): 'id_attribute'
|
||||
}
|
||||
|
||||
# Get the final name for an action named 'id'
|
||||
name = renames.get(('action', 'id'), 'id')
|
||||
|
||||
:type shape: botocore.model.Shape
|
||||
:param shape: The underlying shape for this resource.
|
||||
"""
|
||||
# Meta is a reserved name for resources
|
||||
names = {'meta'}
|
||||
self._renamed = {}
|
||||
|
||||
if self._definition.get('load'):
|
||||
names.add('load')
|
||||
|
||||
for item in self._definition.get('identifiers', []):
|
||||
self._load_name_with_category(names, item['name'], 'identifier')
|
||||
|
||||
for name in self._definition.get('actions', {}):
|
||||
self._load_name_with_category(names, name, 'action')
|
||||
|
||||
for name, ref in self._get_has_definition().items():
|
||||
# Subresources require no data members, just typically
|
||||
# identifiers and user input.
|
||||
data_required = False
|
||||
for identifier in ref['resource']['identifiers']:
|
||||
if identifier['source'] == 'data':
|
||||
data_required = True
|
||||
break
|
||||
|
||||
if not data_required:
|
||||
self._load_name_with_category(
|
||||
names, name, 'subresource', snake_case=False
|
||||
)
|
||||
else:
|
||||
self._load_name_with_category(names, name, 'reference')
|
||||
|
||||
for name in self._definition.get('hasMany', {}):
|
||||
self._load_name_with_category(names, name, 'collection')
|
||||
|
||||
for name in self._definition.get('waiters', {}):
|
||||
self._load_name_with_category(
|
||||
names, Waiter.PREFIX + name, 'waiter'
|
||||
)
|
||||
|
||||
if shape is not None:
|
||||
for name in shape.members.keys():
|
||||
self._load_name_with_category(names, name, 'attribute')
|
||||
|
||||
def _load_name_with_category(self, names, name, category, snake_case=True):
|
||||
"""
|
||||
Load a name with a given category, possibly renaming it
|
||||
if that name is already in use. The name will be stored
|
||||
in ``names`` and possibly be set up in ``self._renamed``.
|
||||
|
||||
:type names: set
|
||||
:param names: Existing names (Python attributes, properties, or
|
||||
methods) on the resource.
|
||||
:type name: string
|
||||
:param name: The original name of the value.
|
||||
:type category: string
|
||||
:param category: The value type, such as 'identifier' or 'action'
|
||||
:type snake_case: bool
|
||||
:param snake_case: True (default) if the name should be snake cased.
|
||||
"""
|
||||
if snake_case:
|
||||
name = xform_name(name)
|
||||
|
||||
if name in names:
|
||||
logger.debug(f'Renaming {self.name} {category} {name}')
|
||||
self._renamed[(category, name)] = name + '_' + category
|
||||
name += '_' + category
|
||||
|
||||
if name in names:
|
||||
# This isn't good, let's raise instead of trying to keep
|
||||
# renaming this value.
|
||||
raise ValueError(
|
||||
f'Problem renaming {self.name} {category} to {name}!'
|
||||
)
|
||||
|
||||
names.add(name)
|
||||
|
||||
def _get_name(self, category, name, snake_case=True):
|
||||
"""
|
||||
Get a possibly renamed value given a category and name. This
|
||||
uses the rename map set up in ``load_rename_map``, so that
|
||||
method must be called once first.
|
||||
|
||||
:type category: string
|
||||
:param category: The value type, such as 'identifier' or 'action'
|
||||
:type name: string
|
||||
:param name: The original name of the value
|
||||
:type snake_case: bool
|
||||
:param snake_case: True (default) if the name should be snake cased.
|
||||
:rtype: string
|
||||
:return: Either the renamed value if it is set, otherwise the
|
||||
original name.
|
||||
"""
|
||||
if snake_case:
|
||||
name = xform_name(name)
|
||||
|
||||
return self._renamed.get((category, name), name)
|
||||
|
||||
def get_attributes(self, shape):
|
||||
"""
|
||||
Get a dictionary of attribute names to original name and shape
|
||||
models that represent the attributes of this resource. Looks
|
||||
like the following:
|
||||
|
||||
{
|
||||
'some_name': ('SomeName', <Shape...>)
|
||||
}
|
||||
|
||||
:type shape: botocore.model.Shape
|
||||
:param shape: The underlying shape for this resource.
|
||||
:rtype: dict
|
||||
:return: Mapping of resource attributes.
|
||||
"""
|
||||
attributes = {}
|
||||
identifier_names = [i.name for i in self.identifiers]
|
||||
|
||||
for name, member in shape.members.items():
|
||||
snake_cased = xform_name(name)
|
||||
if snake_cased in identifier_names:
|
||||
# Skip identifiers, these are set through other means
|
||||
continue
|
||||
snake_cased = self._get_name(
|
||||
'attribute', snake_cased, snake_case=False
|
||||
)
|
||||
attributes[snake_cased] = (name, member)
|
||||
|
||||
return attributes
|
||||
|
||||
@property
|
||||
def identifiers(self):
|
||||
"""
|
||||
Get a list of resource identifiers.
|
||||
|
||||
:type: list(:py:class:`Identifier`)
|
||||
"""
|
||||
identifiers = []
|
||||
|
||||
for item in self._definition.get('identifiers', []):
|
||||
name = self._get_name('identifier', item['name'])
|
||||
member_name = item.get('memberName', None)
|
||||
if member_name:
|
||||
member_name = self._get_name('attribute', member_name)
|
||||
identifiers.append(Identifier(name, member_name))
|
||||
|
||||
return identifiers
|
||||
|
||||
@property
|
||||
def load(self):
|
||||
"""
|
||||
Get the load action for this resource, if it is defined.
|
||||
|
||||
:type: :py:class:`Action` or ``None``
|
||||
"""
|
||||
action = self._definition.get('load')
|
||||
|
||||
if action is not None:
|
||||
action = Action('load', action, self._resource_defs)
|
||||
|
||||
return action
|
||||
|
||||
@property
|
||||
def actions(self):
|
||||
"""
|
||||
Get a list of actions for this resource.
|
||||
|
||||
:type: list(:py:class:`Action`)
|
||||
"""
|
||||
actions = []
|
||||
|
||||
for name, item in self._definition.get('actions', {}).items():
|
||||
name = self._get_name('action', name)
|
||||
actions.append(Action(name, item, self._resource_defs))
|
||||
|
||||
return actions
|
||||
|
||||
@property
|
||||
def batch_actions(self):
|
||||
"""
|
||||
Get a list of batch actions for this resource.
|
||||
|
||||
:type: list(:py:class:`Action`)
|
||||
"""
|
||||
actions = []
|
||||
|
||||
for name, item in self._definition.get('batchActions', {}).items():
|
||||
name = self._get_name('batch_action', name)
|
||||
actions.append(Action(name, item, self._resource_defs))
|
||||
|
||||
return actions
|
||||
|
||||
def _get_has_definition(self):
|
||||
"""
|
||||
Get a ``has`` relationship definition from a model, where the
|
||||
service resource model is treated special in that it contains
|
||||
a relationship to every resource defined for the service. This
|
||||
allows things like ``s3.Object('bucket-name', 'key')`` to
|
||||
work even though the JSON doesn't define it explicitly.
|
||||
|
||||
:rtype: dict
|
||||
:return: Mapping of names to subresource and reference
|
||||
definitions.
|
||||
"""
|
||||
if self.name not in self._resource_defs:
|
||||
# This is the service resource, so let us expose all of
|
||||
# the defined resources as subresources.
|
||||
definition = {}
|
||||
|
||||
for name, resource_def in self._resource_defs.items():
|
||||
# It's possible for the service to have renamed a
|
||||
# resource or to have defined multiple names that
|
||||
# point to the same resource type, so we need to
|
||||
# take that into account.
|
||||
found = False
|
||||
has_items = self._definition.get('has', {}).items()
|
||||
for has_name, has_def in has_items:
|
||||
if has_def.get('resource', {}).get('type') == name:
|
||||
definition[has_name] = has_def
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
# Create a relationship definition and attach it
|
||||
# to the model, such that all identifiers must be
|
||||
# supplied by the user. It will look something like:
|
||||
#
|
||||
# {
|
||||
# 'resource': {
|
||||
# 'type': 'ResourceName',
|
||||
# 'identifiers': [
|
||||
# {'target': 'Name1', 'source': 'input'},
|
||||
# {'target': 'Name2', 'source': 'input'},
|
||||
# ...
|
||||
# ]
|
||||
# }
|
||||
# }
|
||||
#
|
||||
fake_has = {'resource': {'type': name, 'identifiers': []}}
|
||||
|
||||
for identifier in resource_def.get('identifiers', []):
|
||||
fake_has['resource']['identifiers'].append(
|
||||
{'target': identifier['name'], 'source': 'input'}
|
||||
)
|
||||
|
||||
definition[name] = fake_has
|
||||
else:
|
||||
definition = self._definition.get('has', {})
|
||||
|
||||
return definition
|
||||
|
||||
def _get_related_resources(self, subresources):
|
||||
"""
|
||||
Get a list of sub-resources or references.
|
||||
|
||||
:type subresources: bool
|
||||
:param subresources: ``True`` to get sub-resources, ``False`` to
|
||||
get references.
|
||||
:rtype: list(:py:class:`Action`)
|
||||
"""
|
||||
resources = []
|
||||
|
||||
for name, definition in self._get_has_definition().items():
|
||||
if subresources:
|
||||
name = self._get_name('subresource', name, snake_case=False)
|
||||
else:
|
||||
name = self._get_name('reference', name)
|
||||
action = Action(name, definition, self._resource_defs)
|
||||
|
||||
data_required = False
|
||||
for identifier in action.resource.identifiers:
|
||||
if identifier.source == 'data':
|
||||
data_required = True
|
||||
break
|
||||
|
||||
if subresources and not data_required:
|
||||
resources.append(action)
|
||||
elif not subresources and data_required:
|
||||
resources.append(action)
|
||||
|
||||
return resources
|
||||
|
||||
@property
|
||||
def subresources(self):
|
||||
"""
|
||||
Get a list of sub-resources.
|
||||
|
||||
:type: list(:py:class:`Action`)
|
||||
"""
|
||||
return self._get_related_resources(True)
|
||||
|
||||
@property
|
||||
def references(self):
|
||||
"""
|
||||
Get a list of reference resources.
|
||||
|
||||
:type: list(:py:class:`Action`)
|
||||
"""
|
||||
return self._get_related_resources(False)
|
||||
|
||||
@property
|
||||
def collections(self):
|
||||
"""
|
||||
Get a list of collections for this resource.
|
||||
|
||||
:type: list(:py:class:`Collection`)
|
||||
"""
|
||||
collections = []
|
||||
|
||||
for name, item in self._definition.get('hasMany', {}).items():
|
||||
name = self._get_name('collection', name)
|
||||
collections.append(Collection(name, item, self._resource_defs))
|
||||
|
||||
return collections
|
||||
|
||||
@property
|
||||
def waiters(self):
|
||||
"""
|
||||
Get a list of waiters for this resource.
|
||||
|
||||
:type: list(:py:class:`Waiter`)
|
||||
"""
|
||||
waiters = []
|
||||
|
||||
for name, item in self._definition.get('waiters', {}).items():
|
||||
name = self._get_name('waiter', Waiter.PREFIX + name)
|
||||
waiters.append(Waiter(name, item))
|
||||
|
||||
return waiters
|
||||
167
cdk-env/lib/python3.12/site-packages/boto3/resources/params.py
Normal file
167
cdk-env/lib/python3.12/site-packages/boto3/resources/params.py
Normal file
@@ -0,0 +1,167 @@
|
||||
# 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 re
|
||||
|
||||
import jmespath
|
||||
from botocore import xform_name
|
||||
|
||||
from ..exceptions import ResourceLoadException
|
||||
|
||||
INDEX_RE = re.compile(r'\[(.*)\]$')
|
||||
|
||||
|
||||
def get_data_member(parent, path):
|
||||
"""
|
||||
Get a data member from a parent using a JMESPath search query,
|
||||
loading the parent if required. If the parent cannot be loaded
|
||||
and no data is present then an exception is raised.
|
||||
|
||||
:type parent: ServiceResource
|
||||
:param parent: The resource instance to which contains data we
|
||||
are interested in.
|
||||
:type path: string
|
||||
:param path: The JMESPath expression to query
|
||||
:raises ResourceLoadException: When no data is present and the
|
||||
resource cannot be loaded.
|
||||
:returns: The queried data or ``None``.
|
||||
"""
|
||||
# Ensure the parent has its data loaded, if possible.
|
||||
if parent.meta.data is None:
|
||||
if hasattr(parent, 'load'):
|
||||
parent.load()
|
||||
else:
|
||||
raise ResourceLoadException(
|
||||
f'{parent.__class__.__name__} has no load method!'
|
||||
)
|
||||
|
||||
return jmespath.search(path, parent.meta.data)
|
||||
|
||||
|
||||
def create_request_parameters(parent, request_model, params=None, index=None):
|
||||
"""
|
||||
Handle request parameters that can be filled in from identifiers,
|
||||
resource data members or constants.
|
||||
|
||||
By passing ``params``, you can invoke this method multiple times and
|
||||
build up a parameter dict over time, which is particularly useful
|
||||
for reverse JMESPath expressions that append to lists.
|
||||
|
||||
:type parent: ServiceResource
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
:type request_model: :py:class:`~boto3.resources.model.Request`
|
||||
:param request_model: The action request model.
|
||||
:type params: dict
|
||||
:param params: If set, then add to this existing dict. It is both
|
||||
edited in-place and returned.
|
||||
:type index: int
|
||||
:param index: The position of an item within a list
|
||||
:rtype: dict
|
||||
:return: Pre-filled parameters to be sent to the request operation.
|
||||
"""
|
||||
if params is None:
|
||||
params = {}
|
||||
|
||||
for param in request_model.params:
|
||||
source = param.source
|
||||
target = param.target
|
||||
|
||||
if source == 'identifier':
|
||||
# Resource identifier, e.g. queue.url
|
||||
value = getattr(parent, xform_name(param.name))
|
||||
elif source == 'data':
|
||||
# If this is a data member then it may incur a load
|
||||
# action before returning the value.
|
||||
value = get_data_member(parent, param.path)
|
||||
elif source in ['string', 'integer', 'boolean']:
|
||||
# These are hard-coded values in the definition
|
||||
value = param.value
|
||||
elif source == 'input':
|
||||
# This is provided by the user, so ignore it here
|
||||
continue
|
||||
else:
|
||||
raise NotImplementedError(f'Unsupported source type: {source}')
|
||||
|
||||
build_param_structure(params, target, value, index)
|
||||
|
||||
return params
|
||||
|
||||
|
||||
def build_param_structure(params, target, value, index=None):
|
||||
"""
|
||||
This method provides a basic reverse JMESPath implementation that
|
||||
lets you go from a JMESPath-like string to a possibly deeply nested
|
||||
object. The ``params`` are mutated in-place, so subsequent calls
|
||||
can modify the same element by its index.
|
||||
|
||||
>>> build_param_structure(params, 'test[0]', 1)
|
||||
>>> print(params)
|
||||
{'test': [1]}
|
||||
|
||||
>>> build_param_structure(params, 'foo.bar[0].baz', 'hello world')
|
||||
>>> print(params)
|
||||
{'test': [1], 'foo': {'bar': [{'baz': 'hello, world'}]}}
|
||||
|
||||
"""
|
||||
pos = params
|
||||
parts = target.split('.')
|
||||
|
||||
# First, split into parts like 'foo', 'bar[0]', 'baz' and process
|
||||
# each piece. It can either be a list or a dict, depending on if
|
||||
# an index like `[0]` is present. We detect this via a regular
|
||||
# expression, and keep track of where we are in params via the
|
||||
# pos variable, walking down to the last item. Once there, we
|
||||
# set the value.
|
||||
for i, part in enumerate(parts):
|
||||
# Is it indexing an array?
|
||||
result = INDEX_RE.search(part)
|
||||
if result:
|
||||
if result.group(1):
|
||||
if result.group(1) == '*':
|
||||
part = part[:-3]
|
||||
else:
|
||||
# We have an explicit index
|
||||
index = int(result.group(1))
|
||||
part = part[: -len(str(index) + '[]')]
|
||||
else:
|
||||
# Index will be set after we know the proper part
|
||||
# name and that it's a list instance.
|
||||
index = None
|
||||
part = part[:-2]
|
||||
|
||||
if part not in pos or not isinstance(pos[part], list):
|
||||
pos[part] = []
|
||||
|
||||
# This means we should append, e.g. 'foo[]'
|
||||
if index is None:
|
||||
index = len(pos[part])
|
||||
|
||||
while len(pos[part]) <= index:
|
||||
# Assume it's a dict until we set the final value below
|
||||
pos[part].append({})
|
||||
|
||||
# Last item? Set the value, otherwise set the new position
|
||||
if i == len(parts) - 1:
|
||||
pos[part][index] = value
|
||||
else:
|
||||
# The new pos is the *item* in the array, not the array!
|
||||
pos = pos[part][index]
|
||||
else:
|
||||
if part not in pos:
|
||||
pos[part] = {}
|
||||
|
||||
# Last item? Set the value, otherwise set the new position
|
||||
if i == len(parts) - 1:
|
||||
pos[part] = value
|
||||
else:
|
||||
pos = pos[part]
|
||||
316
cdk-env/lib/python3.12/site-packages/boto3/resources/response.py
Normal file
316
cdk-env/lib/python3.12/site-packages/boto3/resources/response.py
Normal file
@@ -0,0 +1,316 @@
|
||||
# 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 jmespath
|
||||
from botocore import xform_name
|
||||
|
||||
from .params import get_data_member
|
||||
|
||||
|
||||
def all_not_none(iterable):
|
||||
"""
|
||||
Return True if all elements of the iterable are not None (or if the
|
||||
iterable is empty). This is like the built-in ``all``, except checks
|
||||
against None, so 0 and False are allowable values.
|
||||
"""
|
||||
for element in iterable:
|
||||
if element is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def build_identifiers(identifiers, parent, params=None, raw_response=None):
|
||||
"""
|
||||
Builds a mapping of identifier names to values based on the
|
||||
identifier source location, type, and target. Identifier
|
||||
values may be scalars or lists depending on the source type
|
||||
and location.
|
||||
|
||||
:type identifiers: list
|
||||
:param identifiers: List of :py:class:`~boto3.resources.model.Parameter`
|
||||
definitions
|
||||
:type parent: ServiceResource
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
:type params: dict
|
||||
:param params: Request parameters sent to the service.
|
||||
:type raw_response: dict
|
||||
:param raw_response: Low-level operation response.
|
||||
:rtype: list
|
||||
:return: An ordered list of ``(name, value)`` identifier tuples.
|
||||
"""
|
||||
results = []
|
||||
|
||||
for identifier in identifiers:
|
||||
source = identifier.source
|
||||
target = identifier.target
|
||||
|
||||
if source == 'response':
|
||||
value = jmespath.search(identifier.path, raw_response)
|
||||
elif source == 'requestParameter':
|
||||
value = jmespath.search(identifier.path, params)
|
||||
elif source == 'identifier':
|
||||
value = getattr(parent, xform_name(identifier.name))
|
||||
elif source == 'data':
|
||||
# If this is a data member then it may incur a load
|
||||
# action before returning the value.
|
||||
value = get_data_member(parent, identifier.path)
|
||||
elif source == 'input':
|
||||
# This value is set by the user, so ignore it here
|
||||
continue
|
||||
else:
|
||||
raise NotImplementedError(f'Unsupported source type: {source}')
|
||||
|
||||
results.append((xform_name(target), value))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def build_empty_response(search_path, operation_name, service_model):
|
||||
"""
|
||||
Creates an appropriate empty response for the type that is expected,
|
||||
based on the service model's shape type. For example, a value that
|
||||
is normally a list would then return an empty list. A structure would
|
||||
return an empty dict, and a number would return None.
|
||||
|
||||
:type search_path: string
|
||||
:param search_path: JMESPath expression to search in the response
|
||||
:type operation_name: string
|
||||
:param operation_name: Name of the underlying service operation.
|
||||
:type service_model: :ref:`botocore.model.ServiceModel`
|
||||
:param service_model: The Botocore service model
|
||||
:rtype: dict, list, or None
|
||||
:return: An appropriate empty value
|
||||
"""
|
||||
response = None
|
||||
|
||||
operation_model = service_model.operation_model(operation_name)
|
||||
shape = operation_model.output_shape
|
||||
|
||||
if search_path:
|
||||
# Walk the search path and find the final shape. For example, given
|
||||
# a path of ``foo.bar[0].baz``, we first find the shape for ``foo``,
|
||||
# then the shape for ``bar`` (ignoring the indexing), and finally
|
||||
# the shape for ``baz``.
|
||||
for item in search_path.split('.'):
|
||||
item = item.strip('[0123456789]$')
|
||||
|
||||
if shape.type_name == 'structure':
|
||||
shape = shape.members[item]
|
||||
elif shape.type_name == 'list':
|
||||
shape = shape.member
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f'Search path hits shape type {shape.type_name} from {item}'
|
||||
)
|
||||
|
||||
# Anything not handled here is set to None
|
||||
if shape.type_name == 'structure':
|
||||
response = {}
|
||||
elif shape.type_name == 'list':
|
||||
response = []
|
||||
elif shape.type_name == 'map':
|
||||
response = {}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class RawHandler:
|
||||
"""
|
||||
A raw action response handler. This passed through the response
|
||||
dictionary, optionally after performing a JMESPath search if one
|
||||
has been defined for the action.
|
||||
|
||||
:type search_path: string
|
||||
:param search_path: JMESPath expression to search in the response
|
||||
:rtype: dict
|
||||
:return: Service response
|
||||
"""
|
||||
|
||||
def __init__(self, search_path):
|
||||
self.search_path = search_path
|
||||
|
||||
def __call__(self, parent, params, response):
|
||||
"""
|
||||
:type parent: ServiceResource
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
:type params: dict
|
||||
:param params: Request parameters sent to the service.
|
||||
:type response: dict
|
||||
:param response: Low-level operation response.
|
||||
"""
|
||||
# TODO: Remove the '$' check after JMESPath supports it
|
||||
if self.search_path and self.search_path != '$':
|
||||
response = jmespath.search(self.search_path, response)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class ResourceHandler:
|
||||
"""
|
||||
Creates a new resource or list of new resources from the low-level
|
||||
response based on the given response resource definition.
|
||||
|
||||
:type search_path: string
|
||||
:param search_path: JMESPath expression to search in the response
|
||||
|
||||
:type factory: ResourceFactory
|
||||
:param factory: The factory that created the resource class to which
|
||||
this action is attached.
|
||||
|
||||
:type resource_model: :py:class:`~boto3.resources.model.ResponseResource`
|
||||
:param resource_model: Response resource model.
|
||||
|
||||
:type service_context: :py:class:`~boto3.utils.ServiceContext`
|
||||
:param service_context: Context about the AWS service
|
||||
|
||||
:type operation_name: string
|
||||
:param operation_name: Name of the underlying service operation, if it
|
||||
exists.
|
||||
|
||||
:rtype: ServiceResource or list
|
||||
:return: New resource instance(s).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
search_path,
|
||||
factory,
|
||||
resource_model,
|
||||
service_context,
|
||||
operation_name=None,
|
||||
):
|
||||
self.search_path = search_path
|
||||
self.factory = factory
|
||||
self.resource_model = resource_model
|
||||
self.operation_name = operation_name
|
||||
self.service_context = service_context
|
||||
|
||||
def __call__(self, parent, params, response):
|
||||
"""
|
||||
:type parent: ServiceResource
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
:type params: dict
|
||||
:param params: Request parameters sent to the service.
|
||||
:type response: dict
|
||||
:param response: Low-level operation response.
|
||||
"""
|
||||
resource_name = self.resource_model.type
|
||||
json_definition = self.service_context.resource_json_definitions.get(
|
||||
resource_name
|
||||
)
|
||||
|
||||
# Load the new resource class that will result from this action.
|
||||
resource_cls = self.factory.load_from_definition(
|
||||
resource_name=resource_name,
|
||||
single_resource_json_definition=json_definition,
|
||||
service_context=self.service_context,
|
||||
)
|
||||
raw_response = response
|
||||
search_response = None
|
||||
|
||||
# Anytime a path is defined, it means the response contains the
|
||||
# resource's attributes, so resource_data gets set here. It
|
||||
# eventually ends up in resource.meta.data, which is where
|
||||
# the attribute properties look for data.
|
||||
if self.search_path:
|
||||
search_response = jmespath.search(self.search_path, raw_response)
|
||||
|
||||
# First, we parse all the identifiers, then create the individual
|
||||
# response resources using them. Any identifiers that are lists
|
||||
# will have one item consumed from the front of the list for each
|
||||
# resource that is instantiated. Items which are not a list will
|
||||
# be set as the same value on each new resource instance.
|
||||
identifiers = dict(
|
||||
build_identifiers(
|
||||
self.resource_model.identifiers, parent, params, raw_response
|
||||
)
|
||||
)
|
||||
|
||||
# If any of the identifiers is a list, then the response is plural
|
||||
plural = [v for v in identifiers.values() if isinstance(v, list)]
|
||||
|
||||
if plural:
|
||||
response = []
|
||||
|
||||
# The number of items in an identifier that is a list will
|
||||
# determine how many resource instances to create.
|
||||
for i in range(len(plural[0])):
|
||||
# Response item data is *only* available if a search path
|
||||
# was given. This prevents accidentally loading unrelated
|
||||
# data that may be in the response.
|
||||
response_item = None
|
||||
if search_response:
|
||||
response_item = search_response[i]
|
||||
response.append(
|
||||
self.handle_response_item(
|
||||
resource_cls, parent, identifiers, response_item
|
||||
)
|
||||
)
|
||||
elif all_not_none(identifiers.values()):
|
||||
# All identifiers must always exist, otherwise the resource
|
||||
# cannot be instantiated.
|
||||
response = self.handle_response_item(
|
||||
resource_cls, parent, identifiers, search_response
|
||||
)
|
||||
else:
|
||||
# The response should be empty, but that may mean an
|
||||
# empty dict, list, or None based on whether we make
|
||||
# a remote service call and what shape it is expected
|
||||
# to return.
|
||||
response = None
|
||||
if self.operation_name is not None:
|
||||
# A remote service call was made, so try and determine
|
||||
# its shape.
|
||||
response = build_empty_response(
|
||||
self.search_path,
|
||||
self.operation_name,
|
||||
self.service_context.service_model,
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
def handle_response_item(
|
||||
self, resource_cls, parent, identifiers, resource_data
|
||||
):
|
||||
"""
|
||||
Handles the creation of a single response item by setting
|
||||
parameters and creating the appropriate resource instance.
|
||||
|
||||
:type resource_cls: ServiceResource subclass
|
||||
:param resource_cls: The resource class to instantiate.
|
||||
:type parent: ServiceResource
|
||||
:param parent: The resource instance to which this action is attached.
|
||||
:type identifiers: dict
|
||||
:param identifiers: Map of identifier names to value or values.
|
||||
:type resource_data: dict or None
|
||||
:param resource_data: Data for resource attributes.
|
||||
:rtype: ServiceResource
|
||||
:return: New resource instance.
|
||||
"""
|
||||
kwargs = {
|
||||
'client': parent.meta.client,
|
||||
}
|
||||
|
||||
for name, value in identifiers.items():
|
||||
# If value is a list, then consume the next item
|
||||
if isinstance(value, list):
|
||||
value = value.pop(0)
|
||||
|
||||
kwargs[name] = value
|
||||
|
||||
resource = resource_cls(**kwargs)
|
||||
|
||||
if resource_data is not None:
|
||||
resource.meta.data = resource_data
|
||||
|
||||
return resource
|
||||
Reference in New Issue
Block a user