2025-12-01
This commit is contained in:
@@ -0,0 +1,564 @@
|
||||
# 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 os
|
||||
|
||||
import botocore.session
|
||||
from botocore.client import Config
|
||||
from botocore.exceptions import (
|
||||
DataNotFoundError,
|
||||
NoCredentialsError,
|
||||
UnknownServiceError,
|
||||
)
|
||||
|
||||
import boto3
|
||||
import boto3.utils
|
||||
from boto3.exceptions import ResourceNotExistsError, UnknownAPIVersionError
|
||||
|
||||
from .resources.factory import ResourceFactory
|
||||
|
||||
|
||||
class Session:
|
||||
"""
|
||||
A session stores configuration state and allows you to create service
|
||||
clients and resources.
|
||||
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: AWS access key ID
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: AWS secret access key
|
||||
:type aws_session_token: string
|
||||
:param aws_session_token: AWS temporary session token
|
||||
:type region_name: string
|
||||
:param region_name: Default region when creating new connections
|
||||
:type botocore_session: botocore.session.Session
|
||||
:param botocore_session: Use this Botocore session instead of creating
|
||||
a new default one.
|
||||
:type profile_name: string
|
||||
:param profile_name: The name of a profile to use. If not given, then
|
||||
the default profile is used.
|
||||
:type aws_account_id: string
|
||||
:param aws_account_id: AWS account ID
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
aws_session_token=None,
|
||||
region_name=None,
|
||||
botocore_session=None,
|
||||
profile_name=None,
|
||||
aws_account_id=None,
|
||||
):
|
||||
if botocore_session is not None:
|
||||
self._session = botocore_session
|
||||
else:
|
||||
# Create a new default session
|
||||
self._session = botocore.session.get_session()
|
||||
|
||||
# Setup custom user-agent string if it isn't already customized
|
||||
if self._session.user_agent_name == 'Botocore':
|
||||
botocore_info = f'Botocore/{self._session.user_agent_version}'
|
||||
if self._session.user_agent_extra:
|
||||
self._session.user_agent_extra += ' ' + botocore_info
|
||||
else:
|
||||
self._session.user_agent_extra = botocore_info
|
||||
self._session.user_agent_name = 'Boto3'
|
||||
self._session.user_agent_version = boto3.__version__
|
||||
|
||||
if profile_name is not None:
|
||||
self._session.set_config_variable('profile', profile_name)
|
||||
|
||||
creds = (
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
aws_session_token,
|
||||
aws_account_id,
|
||||
)
|
||||
if any(creds):
|
||||
if self._account_id_set_without_credentials(
|
||||
aws_account_id, aws_access_key_id, aws_secret_access_key
|
||||
):
|
||||
raise NoCredentialsError()
|
||||
self._session.set_credentials(
|
||||
aws_access_key_id,
|
||||
aws_secret_access_key,
|
||||
aws_session_token,
|
||||
aws_account_id,
|
||||
)
|
||||
|
||||
if region_name is not None:
|
||||
self._session.set_config_variable('region', region_name)
|
||||
|
||||
self.resource_factory = ResourceFactory(
|
||||
self._session.get_component('event_emitter')
|
||||
)
|
||||
self._setup_loader()
|
||||
self._register_default_handlers()
|
||||
|
||||
def __repr__(self):
|
||||
return '{}(region_name={})'.format(
|
||||
self.__class__.__name__,
|
||||
repr(self._session.get_config_variable('region')),
|
||||
)
|
||||
|
||||
@property
|
||||
def profile_name(self):
|
||||
"""
|
||||
The **read-only** profile name.
|
||||
"""
|
||||
return self._session.profile or 'default'
|
||||
|
||||
@property
|
||||
def region_name(self):
|
||||
"""
|
||||
The **read-only** region name.
|
||||
"""
|
||||
return self._session.get_config_variable('region')
|
||||
|
||||
@property
|
||||
def events(self):
|
||||
"""
|
||||
The event emitter for a session
|
||||
"""
|
||||
return self._session.get_component('event_emitter')
|
||||
|
||||
@property
|
||||
def available_profiles(self):
|
||||
"""
|
||||
The profiles available to the session credentials
|
||||
"""
|
||||
return self._session.available_profiles
|
||||
|
||||
def _setup_loader(self):
|
||||
"""
|
||||
Setup loader paths so that we can load resources.
|
||||
"""
|
||||
self._loader = self._session.get_component('data_loader')
|
||||
self._loader.search_paths.append(
|
||||
os.path.join(os.path.dirname(__file__), 'data')
|
||||
)
|
||||
|
||||
def get_available_services(self):
|
||||
"""
|
||||
Get a list of available services that can be loaded as low-level
|
||||
clients via :py:meth:`Session.client`.
|
||||
|
||||
:rtype: list
|
||||
:return: List of service names
|
||||
"""
|
||||
return self._session.get_available_services()
|
||||
|
||||
def get_available_resources(self):
|
||||
"""
|
||||
Get a list of available services that can be loaded as resource
|
||||
clients via :py:meth:`Session.resource`.
|
||||
|
||||
:rtype: list
|
||||
:return: List of service names
|
||||
"""
|
||||
return self._loader.list_available_services(type_name='resources-1')
|
||||
|
||||
def get_available_partitions(self):
|
||||
"""Lists the available partitions
|
||||
|
||||
:rtype: list
|
||||
:return: Returns a list of partition names (e.g., ["aws", "aws-cn"])
|
||||
"""
|
||||
return self._session.get_available_partitions()
|
||||
|
||||
def get_available_regions(
|
||||
self, service_name, partition_name='aws', allow_non_regional=False
|
||||
):
|
||||
"""Lists the region and endpoint names of a particular partition.
|
||||
|
||||
The list of regions returned by this method are regions that are
|
||||
explicitly known by the client to exist and is not comprehensive. A
|
||||
region not returned in this list may still be available for the
|
||||
provided service.
|
||||
|
||||
:type service_name: string
|
||||
:param service_name: Name of a service to list endpoint for (e.g., s3).
|
||||
|
||||
:type partition_name: string
|
||||
:param partition_name: Name of the partition to limit endpoints to.
|
||||
(e.g., aws for the public AWS endpoints, aws-cn for AWS China
|
||||
endpoints, aws-us-gov for AWS GovCloud (US) Endpoints, etc.)
|
||||
|
||||
:type allow_non_regional: bool
|
||||
:param allow_non_regional: Set to True to include endpoints that are
|
||||
not regional endpoints (e.g., s3-external-1,
|
||||
fips-us-gov-west-1, etc).
|
||||
|
||||
:return: Returns a list of endpoint names (e.g., ["us-east-1"]).
|
||||
"""
|
||||
return self._session.get_available_regions(
|
||||
service_name=service_name,
|
||||
partition_name=partition_name,
|
||||
allow_non_regional=allow_non_regional,
|
||||
)
|
||||
|
||||
def get_credentials(self):
|
||||
"""
|
||||
Return the :class:`botocore.credentials.Credentials` object
|
||||
associated with this session. If the credentials have not
|
||||
yet been loaded, this will attempt to load them. If they
|
||||
have already been loaded, this will return the cached
|
||||
credentials.
|
||||
"""
|
||||
return self._session.get_credentials()
|
||||
|
||||
def get_partition_for_region(self, region_name):
|
||||
"""Lists the partition name of a particular region.
|
||||
|
||||
:type region_name: string
|
||||
:param region_name: Name of the region to list partition for (e.g.,
|
||||
us-east-1).
|
||||
|
||||
:rtype: string
|
||||
:return: Returns the respective partition name (e.g., aws).
|
||||
"""
|
||||
return self._session.get_partition_for_region(region_name)
|
||||
|
||||
def client(
|
||||
self,
|
||||
service_name,
|
||||
region_name=None,
|
||||
api_version=None,
|
||||
use_ssl=True,
|
||||
verify=None,
|
||||
endpoint_url=None,
|
||||
aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
aws_session_token=None,
|
||||
config=None,
|
||||
aws_account_id=None,
|
||||
):
|
||||
"""
|
||||
Create a low-level service client by name.
|
||||
|
||||
:type service_name: string
|
||||
:param service_name: The name of a service, e.g. 's3' or 'ec2'. You
|
||||
can get a list of available services via
|
||||
:py:meth:`get_available_services`.
|
||||
|
||||
:type region_name: string
|
||||
:param region_name: The name of the region associated with the client.
|
||||
A client is associated with a single region.
|
||||
|
||||
:type api_version: string
|
||||
:param api_version: The API version to use. By default, botocore will
|
||||
use the latest API version when creating a client. You only need
|
||||
to specify this parameter if you want to use a previous API version
|
||||
of the client.
|
||||
|
||||
:type use_ssl: boolean
|
||||
:param use_ssl: Whether or not to use SSL. By default, SSL is used.
|
||||
Note that not all services support non-ssl connections.
|
||||
|
||||
:type verify: boolean/string
|
||||
:param verify: Whether or not to verify SSL certificates. By default
|
||||
SSL certificates are verified. You can provide the following
|
||||
values:
|
||||
|
||||
* False - do not validate SSL certificates. SSL will still be
|
||||
used (unless use_ssl is False), but SSL certificates
|
||||
will not be verified.
|
||||
* path/to/cert/bundle.pem - A filename of the CA cert bundle to
|
||||
uses. You can specify this argument if you want to use a
|
||||
different CA cert bundle than the one used by botocore.
|
||||
|
||||
:type endpoint_url: string
|
||||
:param endpoint_url: The complete URL to use for the constructed
|
||||
client. Normally, botocore will automatically construct the
|
||||
appropriate URL to use when communicating with a service. You
|
||||
can specify a complete URL (including the "http/https" scheme)
|
||||
to override this behavior. If this value is provided,
|
||||
then ``use_ssl`` is ignored.
|
||||
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: The access key to use when creating
|
||||
the client. This is entirely optional, and if not provided,
|
||||
the credentials configured for the session will automatically
|
||||
be used. You only need to provide this argument if you want
|
||||
to override the credentials used for this specific client.
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: The secret key to use when creating
|
||||
the client. Same semantics as aws_access_key_id above.
|
||||
|
||||
:type aws_session_token: string
|
||||
:param aws_session_token: The session token to use when creating
|
||||
the client. Same semantics as aws_access_key_id above.
|
||||
|
||||
:type config: botocore.client.Config
|
||||
:param config: Advanced client configuration options. If region_name
|
||||
is specified in the client config, its value will take precedence
|
||||
over environment variables and configuration values, but not over
|
||||
a region_name value passed explicitly to the method. See
|
||||
`botocore config documentation
|
||||
<https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html>`_
|
||||
for more details.
|
||||
|
||||
:type aws_account_id: string
|
||||
:param aws_account_id: The account id to use when creating
|
||||
the client. Same semantics as aws_access_key_id above.
|
||||
|
||||
:return: Service client instance
|
||||
|
||||
"""
|
||||
return self._session.create_client(
|
||||
service_name,
|
||||
region_name=region_name,
|
||||
api_version=api_version,
|
||||
use_ssl=use_ssl,
|
||||
verify=verify,
|
||||
endpoint_url=endpoint_url,
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
aws_session_token=aws_session_token,
|
||||
config=config,
|
||||
aws_account_id=aws_account_id,
|
||||
)
|
||||
|
||||
def resource(
|
||||
self,
|
||||
service_name,
|
||||
region_name=None,
|
||||
api_version=None,
|
||||
use_ssl=True,
|
||||
verify=None,
|
||||
endpoint_url=None,
|
||||
aws_access_key_id=None,
|
||||
aws_secret_access_key=None,
|
||||
aws_session_token=None,
|
||||
config=None,
|
||||
):
|
||||
"""
|
||||
Create a resource service client by name.
|
||||
|
||||
:type service_name: string
|
||||
:param service_name: The name of a service, e.g. 's3' or 'ec2'. You
|
||||
can get a list of available services via
|
||||
:py:meth:`get_available_resources`.
|
||||
|
||||
:type region_name: string
|
||||
:param region_name: The name of the region associated with the client.
|
||||
A client is associated with a single region.
|
||||
|
||||
:type api_version: string
|
||||
:param api_version: The API version to use. By default, botocore will
|
||||
use the latest API version when creating a client. You only need
|
||||
to specify this parameter if you want to use a previous API version
|
||||
of the client.
|
||||
|
||||
:type use_ssl: boolean
|
||||
:param use_ssl: Whether or not to use SSL. By default, SSL is used.
|
||||
Note that not all services support non-ssl connections.
|
||||
|
||||
:type verify: boolean/string
|
||||
:param verify: Whether or not to verify SSL certificates. By default
|
||||
SSL certificates are verified. You can provide the following
|
||||
values:
|
||||
|
||||
* False - do not validate SSL certificates. SSL will still be
|
||||
used (unless use_ssl is False), but SSL certificates
|
||||
will not be verified.
|
||||
* path/to/cert/bundle.pem - A filename of the CA cert bundle to
|
||||
uses. You can specify this argument if you want to use a
|
||||
different CA cert bundle than the one used by botocore.
|
||||
|
||||
:type endpoint_url: string
|
||||
:param endpoint_url: The complete URL to use for the constructed
|
||||
client. Normally, botocore will automatically construct the
|
||||
appropriate URL to use when communicating with a service. You
|
||||
can specify a complete URL (including the "http/https" scheme)
|
||||
to override this behavior. If this value is provided,
|
||||
then ``use_ssl`` is ignored.
|
||||
|
||||
:type aws_access_key_id: string
|
||||
:param aws_access_key_id: The access key to use when creating
|
||||
the client. This is entirely optional, and if not provided,
|
||||
the credentials configured for the session will automatically
|
||||
be used. You only need to provide this argument if you want
|
||||
to override the credentials used for this specific client.
|
||||
|
||||
:type aws_secret_access_key: string
|
||||
:param aws_secret_access_key: The secret key to use when creating
|
||||
the client. Same semantics as aws_access_key_id above.
|
||||
|
||||
:type aws_session_token: string
|
||||
:param aws_session_token: The session token to use when creating
|
||||
the client. Same semantics as aws_access_key_id above.
|
||||
|
||||
:type config: botocore.client.Config
|
||||
:param config: Advanced client configuration options. If region_name
|
||||
is specified in the client config, its value will take precedence
|
||||
over environment variables and configuration values, but not over
|
||||
a region_name value passed explicitly to the method. If
|
||||
user_agent_extra is specified in the client config, it overrides
|
||||
the default user_agent_extra provided by the resource API. See
|
||||
`botocore config documentation
|
||||
<https://botocore.amazonaws.com/v1/documentation/api/latest/reference/config.html>`_
|
||||
for more details.
|
||||
|
||||
:return: Subclass of :py:class:`~boto3.resources.base.ServiceResource`
|
||||
"""
|
||||
try:
|
||||
resource_model = self._loader.load_service_model(
|
||||
service_name, 'resources-1', api_version
|
||||
)
|
||||
except UnknownServiceError:
|
||||
available = self.get_available_resources()
|
||||
has_low_level_client = (
|
||||
service_name in self.get_available_services()
|
||||
)
|
||||
raise ResourceNotExistsError(
|
||||
service_name, available, has_low_level_client
|
||||
)
|
||||
except DataNotFoundError:
|
||||
# This is because we've provided an invalid API version.
|
||||
available_api_versions = self._loader.list_api_versions(
|
||||
service_name, 'resources-1'
|
||||
)
|
||||
raise UnknownAPIVersionError(
|
||||
service_name, api_version, ', '.join(available_api_versions)
|
||||
)
|
||||
|
||||
if api_version is None:
|
||||
# Even though botocore's load_service_model() can handle
|
||||
# using the latest api_version if not provided, we need
|
||||
# to track this api_version in boto3 in order to ensure
|
||||
# we're pairing a resource model with a client model
|
||||
# of the same API version. It's possible for the latest
|
||||
# API version of a resource model in boto3 to not be
|
||||
# the same API version as a service model in botocore.
|
||||
# So we need to look up the api_version if one is not
|
||||
# provided to ensure we load the same API version of the
|
||||
# client.
|
||||
#
|
||||
# Note: This is relying on the fact that
|
||||
# loader.load_service_model(..., api_version=None)
|
||||
# and loader.determine_latest_version(..., 'resources-1')
|
||||
# both load the same api version of the file.
|
||||
api_version = self._loader.determine_latest_version(
|
||||
service_name, 'resources-1'
|
||||
)
|
||||
|
||||
# Creating a new resource instance requires the low-level client
|
||||
# and service model, the resource version and resource JSON data.
|
||||
# We pass these to the factory and get back a class, which is
|
||||
# instantiated on top of the low-level client.
|
||||
if config is not None:
|
||||
if config.user_agent_extra is None:
|
||||
config = copy.deepcopy(config)
|
||||
config.user_agent_extra = 'Resource'
|
||||
else:
|
||||
config = Config(user_agent_extra='Resource')
|
||||
client = self.client(
|
||||
service_name,
|
||||
region_name=region_name,
|
||||
api_version=api_version,
|
||||
use_ssl=use_ssl,
|
||||
verify=verify,
|
||||
endpoint_url=endpoint_url,
|
||||
aws_access_key_id=aws_access_key_id,
|
||||
aws_secret_access_key=aws_secret_access_key,
|
||||
aws_session_token=aws_session_token,
|
||||
config=config,
|
||||
)
|
||||
service_model = client.meta.service_model
|
||||
|
||||
# Create a ServiceContext object to serve as a reference to
|
||||
# important read-only information about the general service.
|
||||
service_context = boto3.utils.ServiceContext(
|
||||
service_name=service_name,
|
||||
service_model=service_model,
|
||||
resource_json_definitions=resource_model['resources'],
|
||||
service_waiter_model=boto3.utils.LazyLoadedWaiterModel(
|
||||
self._session, service_name, api_version
|
||||
),
|
||||
)
|
||||
|
||||
# Create the service resource class.
|
||||
cls = self.resource_factory.load_from_definition(
|
||||
resource_name=service_name,
|
||||
single_resource_json_definition=resource_model['service'],
|
||||
service_context=service_context,
|
||||
)
|
||||
|
||||
return cls(client=client)
|
||||
|
||||
def _register_default_handlers(self):
|
||||
# S3 customizations
|
||||
self._session.register(
|
||||
'creating-client-class.s3',
|
||||
boto3.utils.lazy_call(
|
||||
'boto3.s3.inject.inject_s3_transfer_methods'
|
||||
),
|
||||
)
|
||||
self._session.register(
|
||||
'creating-resource-class.s3.Bucket',
|
||||
boto3.utils.lazy_call('boto3.s3.inject.inject_bucket_methods'),
|
||||
)
|
||||
self._session.register(
|
||||
'creating-resource-class.s3.Object',
|
||||
boto3.utils.lazy_call('boto3.s3.inject.inject_object_methods'),
|
||||
)
|
||||
self._session.register(
|
||||
'creating-resource-class.s3.ObjectSummary',
|
||||
boto3.utils.lazy_call(
|
||||
'boto3.s3.inject.inject_object_summary_methods'
|
||||
),
|
||||
)
|
||||
|
||||
# DynamoDb customizations
|
||||
self._session.register(
|
||||
'creating-resource-class.dynamodb',
|
||||
boto3.utils.lazy_call(
|
||||
'boto3.dynamodb.transform.register_high_level_interface'
|
||||
),
|
||||
unique_id='high-level-dynamodb',
|
||||
)
|
||||
self._session.register(
|
||||
'creating-resource-class.dynamodb.Table',
|
||||
boto3.utils.lazy_call(
|
||||
'boto3.dynamodb.table.register_table_methods'
|
||||
),
|
||||
unique_id='high-level-dynamodb-table',
|
||||
)
|
||||
|
||||
# EC2 Customizations
|
||||
self._session.register(
|
||||
'creating-resource-class.ec2.ServiceResource',
|
||||
boto3.utils.lazy_call('boto3.ec2.createtags.inject_create_tags'),
|
||||
)
|
||||
|
||||
self._session.register(
|
||||
'creating-resource-class.ec2.Instance',
|
||||
boto3.utils.lazy_call(
|
||||
'boto3.ec2.deletetags.inject_delete_tags',
|
||||
event_emitter=self.events,
|
||||
),
|
||||
)
|
||||
|
||||
def _account_id_set_without_credentials(
|
||||
self, account_id, access_key, secret_key
|
||||
):
|
||||
if account_id is None:
|
||||
return False
|
||||
elif access_key is None or secret_key is None:
|
||||
return True
|
||||
return False
|
||||
Reference in New Issue
Block a user