やったもん勝ち

主にプログラミングのこと。生産性向上の某とかも。

pythonのaws-sdkのboto3を使ってライブラリのコードをちゃんと読んでみる①

boto3とは?

pythonaws-sdkです。
他の言語のaws-sdkは大体aws-sdkみたいな名前で公開されていることが多いのですが、なぜかpythonだけboto3っていう名前です。

boto3.amazonaws.com

個人的にはこのドキュメントすごい読みやすくて好きなライブラリです。
awsapiの設計がちゃんとしててわかりやすいっていうイメージがあります。
あと自分が使い慣れているというものあって、このライブラリを使ってちゃんとコードを読めるようになっていこうと思います。(今までは雰囲気でやっていた。。。)(雰囲気でやっている人も多いはず。)

ドキュメントを読む

boto3.amazonaws.com

一番シンプルそうな、EC2のclientのdescribe_instances()メソッドを例にして見てみます。

まずはドキュメントを見てみます。

response = client.describe_instances(
    Filters=[
        {
            'Name': 'string',
            'Values': [
                'string',
            ]
        },
    ],
    InstanceIds=[
        'string',
    ],
    DryRun=True|False,
    MaxResults=123,
    NextToken='string'
)

この下のparamsの説明でREQUIREDなparamはないので、一旦シンプルに全取得してみます。

>>> import boto3
>>> client = boto3.client("ec2")
>>> response = client.describe_instances()
>>> print(response)

これでEC2の情報が取得できました。(ただし、aws configコマンドで事前にprofileなどは設定しているものとします。)

基本このように、ドキュメントがしっかりしているライブラリはそれ道理にやればうまくいきますね。
(AWSレベルになるとソースコード読む必要性に駆られることなさそう)

ソースコードを読む

github.com

まずは、この一覧の中のboto3っていうディレクトリ配下にソースコードがあるのかな?(これも雰囲気でしかわからないレベル)
f:id:benzenetarou:20190331184846p:plain

正直どこにあるのかよくわからない。
じゃあ実際にpythonから使うときはどうやって読み込まれるのか。
pipでinstallしたライブラリの保存場所とは。

Python Tips:ライブラリ・モジュールの場所を調べたい - Life with Python

これで調べられると。

>>> import boto3
>>> print(boto3.__file__)
/Users/my0shym/.anyenv/envs/pyenv/versions/3.6.0/lib/python3.6/site-packages/boto3/__init__.py

そういえば、anyenvとか使ってたな。なんのパッケージ管理システムを使ってたのかもはやわからなくなっていた。

$ cd /Users/my0shym/.anyenv/envs/pyenv/versions/3.6.0/lib/python3.6/site-packages/boto3/
$ ls -l
total 72
-rw-r--r--   1 my0shym  staff   3338  9 12  2018 __init__.py
drwxr-xr-x   7 my0shym  staff    224  9 12  2018 __pycache__
-rw-r--r--   1 my0shym  staff   1490  9 12  2018 compat.py
drwxr-xr-x  12 my0shym  staff    384  9 12  2018 data
drwxr-xr-x  16 my0shym  staff    512  9 12  2018 docs
drwxr-xr-x   8 my0shym  staff    256  9 12  2018 dynamodb
drwxr-xr-x   6 my0shym  staff    192  9 12  2018 ec2
drwxr-xr-x   4 my0shym  staff    128  9 12  2018 examples
-rw-r--r--   1 my0shym  staff   3993  9 12  2018 exceptions.py
drwxr-xr-x  11 my0shym  staff    352  9 12  2018 resources
drwxr-xr-x   6 my0shym  staff    192  9 12  2018 s3
-rw-r--r--   1 my0shym  staff  19614  9 12  2018 session.py
-rw-r--r--   1 my0shym  staff   3095  9 12  2018 utils.py

これはboto3ディレクトリの下の構造と一緒だ。これでいいのだった。

ライブラリをimportするとまずinit.pyが読み込まれる。
init.pyは110行程度のコード。

# 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
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.

import logging

from boto3.session import Session


__author__ = 'Amazon Web Services'
__version__ = '1.9.2'


# The default Boto3 session; autoloaded when needed.
DEFAULT_SESSION = None


def setup_default_session(**kwargs):
    """
    Set up a default session, passing through any parameters to the session
    constructor. There is no need to call this unless you wish to pass custom
    parameters, because a default session will be created for you.
    """
    global DEFAULT_SESSION
    DEFAULT_SESSION = Session(**kwargs)


def set_stream_logger(name='boto3', level=logging.DEBUG, format_string=None):
    """
    Add a stream handler for the given name and level to the logging module.
    By default, this logs all boto3 messages to ``stdout``.

        >>> import boto3
        >>> boto3.set_stream_logger('boto3.resources', logging.INFO)

    For debugging purposes a good choice is to set the stream logger to ``''``
    which is equivalent to saying "log everything".

    .. WARNING::
       Be aware that when logging anything from ``'botocore'`` the full wire
       trace will appear in your logs. If your payloads contain sensitive data
       this should not be used in production.

    :type name: string
    :param name: Log name
    :type level: int
    :param level: Logging level, e.g. ``logging.INFO``
    :type format_string: str
    :param format_string: Log message format
    """
    if format_string is None:
        format_string = "%(asctime)s %(name)s [%(levelname)s] %(message)s"

    logger = logging.getLogger(name)
    logger.setLevel(level)
    handler = logging.StreamHandler()
    handler.setLevel(level)
    formatter = logging.Formatter(format_string)
    handler.setFormatter(formatter)
    logger.addHandler(handler)


def _get_default_session():
    """
    Get the default session, creating one if needed.

    :rtype: :py:class:`~boto3.session.Session`
    :return: The default session
    """
    if DEFAULT_SESSION is None:
        setup_default_session()

    return DEFAULT_SESSION


def client(*args, **kwargs):
    """
    Create a low-level service client by name using the default session.

    See :py:meth:`boto3.session.Session.client`.
    """
    return _get_default_session().client(*args, **kwargs)


def resource(*args, **kwargs):
    """
    Create a resource service client by name using the default session.

    See :py:meth:`boto3.session.Session.resource`.
    """
    return _get_default_session().resource(*args, **kwargs)


# Set up logging to ``/dev/null`` like a library is supposed to.
# http://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library
class NullHandler(logging.Handler):
    def emit(self, record):
        pass


logging.getLogger('boto3').addHandler(NullHandler())

client関連の部分だけ見てみます。

import logging

from boto3.session import Session


__author__ = 'Amazon Web Services'
__version__ = '1.9.2'


# The default Boto3 session; autoloaded when needed.
DEFAULT_SESSION = None

これが呼び出されます。

def client(*args, **kwargs):
    """
    Create a low-level service client by name using the default session.

    See :py:meth:`boto3.session.Session.client`.
    """
    return _get_default_session().client(*args, **kwargs)
def _get_default_session():
    """
    Get the default session, creating one if needed.

    :rtype: :py:class:`~boto3.session.Session`
    :return: The default session
    """
    if DEFAULT_SESSION is None:
        setup_default_session()

    return DEFAULT_SESSION
def setup_default_session(**kwargs):
    """
    Set up a default session, passing through any parameters to the session
    constructor. There is no need to call this unless you wish to pass custom
    parameters, because a default session will be created for you.
    """
    global DEFAULT_SESSION
    DEFAULT_SESSION = Session(**kwargs)

ここまでででDEFAULT_SESSIONにSessionモジュールが読み込まれて、それに対してclient(*args, **kwargs)メソッド

    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):

        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)

self._session.create_clientは以下より定義されている。

    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):
        if botocore_session is not None:
            self._session = botocore_session
        else:
            # Create a new default session
            self._session = botocore.session.get_session()

つまりbotocore.session.get_session()から。

botocore.session.get_session()のbotocoreのソースコードはどこにあるのか?
調べようと思ったところでタイムアップになってしまった。
続きは次回、あるいは更新していこうと思います。