Inital Commit

main
Brett Woodruff 6 months ago
commit 106024bcb4

Binary file not shown.

@ -0,0 +1,8 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import re
import sys
from charset_normalizer.cli import cli_detect
if __name__ == '__main__':
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.exit(cli_detect())

@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

@ -0,0 +1,183 @@
Metadata-Version: 2.1
Name: boto3
Version: 1.34.39
Summary: The AWS SDK for Python
Home-page: https://github.com/boto/boto3
Author: Amazon Web Services
License: Apache License 2.0
Project-URL: Documentation, https://boto3.amazonaws.com/v1/documentation/api/latest/index.html
Project-URL: Source, https://github.com/boto/boto3
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Natural Language :: English
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >= 3.8
License-File: LICENSE
License-File: NOTICE
Requires-Dist: botocore (<1.35.0,>=1.34.39)
Requires-Dist: jmespath (<2.0.0,>=0.7.1)
Requires-Dist: s3transfer (<0.11.0,>=0.10.0)
Provides-Extra: crt
Requires-Dist: botocore[crt] (<2.0a0,>=1.21.0) ; extra == 'crt'
===============================
Boto3 - The AWS SDK for Python
===============================
|Version| |Python| |License|
Boto3 is the Amazon Web Services (AWS) Software Development Kit (SDK) for
Python, which allows Python developers to write software that makes use
of services like Amazon S3 and Amazon EC2. You can find the latest, most
up to date, documentation at our `doc site`_, including a list of
services that are supported.
Boto3 is maintained and published by `Amazon Web Services`_.
Boto (pronounced boh-toh) was named after the fresh water dolphin native to the Amazon river. The name was chosen by the author of the original Boto library, Mitch Garnaat, as a reference to the company.
Notices
-------
On 2023-12-13, support for Python 3.7 ended for Boto3. This follows the
Python Software Foundation `end of support <https://peps.python.org/pep-0537/#lifespan>`__
for the runtime which occurred on 2023-06-27.
For more information, see this `blog post <https://aws.amazon.com/blogs/developer/python-support-policy-updates-for-aws-sdks-and-tools/>`__.
.. _boto: https://docs.pythonboto.org/
.. _`doc site`: https://boto3.amazonaws.com/v1/documentation/api/latest/index.html
.. _`Amazon Web Services`: https://aws.amazon.com/what-is-aws/
.. |Python| image:: https://img.shields.io/pypi/pyversions/boto3.svg?style=flat
:target: https://pypi.python.org/pypi/boto3/
:alt: Python Versions
.. |Version| image:: http://img.shields.io/pypi/v/boto3.svg?style=flat
:target: https://pypi.python.org/pypi/boto3/
:alt: Package Version
.. |License| image:: http://img.shields.io/pypi/l/boto3.svg?style=flat
:target: https://github.com/boto/boto3/blob/develop/LICENSE
:alt: License
Getting Started
---------------
Assuming that you have a supported version of Python installed, you can first
set up your environment with:
.. code-block:: sh
$ python -m venv .venv
...
$ . .venv/bin/activate
Then, you can install boto3 from PyPI with:
.. code-block:: sh
$ python -m pip install boto3
or install from source with:
.. code-block:: sh
$ git clone https://github.com/boto/boto3.git
$ cd boto3
$ python -m pip install -r requirements.txt
$ python -m pip install -e .
Using Boto3
~~~~~~~~~~~~~~
After installing boto3
Next, set up credentials (in e.g. ``~/.aws/credentials``):
.. code-block:: ini
[default]
aws_access_key_id = YOUR_KEY
aws_secret_access_key = YOUR_SECRET
Then, set up a default region (in e.g. ``~/.aws/config``):
.. code-block:: ini
[default]
region=us-east-1
Other credential configuration methods can be found `here <https://boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html>`__
Then, from a Python interpreter:
.. code-block:: python
>>> import boto3
>>> s3 = boto3.resource('s3')
>>> for bucket in s3.buckets.all():
print(bucket.name)
Running Tests
~~~~~~~~~~~~~
You can run tests in all supported Python versions using ``tox``. By default,
it will run all of the unit and functional tests, but you can also specify your own
``pytest`` options. Note that this requires that you have all supported
versions of Python installed, otherwise you must pass ``-e`` or run the
``pytest`` command directly:
.. code-block:: sh
$ tox
$ tox -- unit/test_session.py
$ tox -e py26,py33 -- integration/
You can also run individual tests with your default Python version:
.. code-block:: sh
$ pytest tests/unit
Getting Help
------------
We use GitHub issues for tracking bugs and feature requests and have limited
bandwidth to address them. Please use these community resources for getting
help:
* Ask a question on `Stack Overflow <https://stackoverflow.com/>`__ and tag it with `boto3 <https://stackoverflow.com/questions/tagged/boto3>`__
* Open a support ticket with `AWS Support <https://console.aws.amazon.com/support/home#/>`__
* If it turns out that you may have found a bug, please `open an issue <https://github.com/boto/boto3/issues/new>`__
Contributing
------------
We value feedback and contributions from our community. Whether it's a bug report, new feature, correction, or additional documentation, we welcome your issues and pull requests. Please read through this `CONTRIBUTING <https://github.com/boto/boto3/blob/develop/CONTRIBUTING.rst>`__ document before submitting any issues or pull requests to ensure we have all the necessary information to effectively respond to your contribution.
Maintenance and Support for SDK Major Versions
----------------------------------------------
Boto3 was made generally available on 06/22/2015 and is currently in the full support phase of the availability life cycle.
For information about maintenance and support for SDK major versions and their underlying dependencies, see the following in the AWS SDKs and Tools Shared Configuration and Credentials Reference Guide:
* `AWS SDKs and Tools Maintenance Policy <https://docs.aws.amazon.com/sdkref/latest/guide/maint-policy.html>`__
* `AWS SDKs and Tools Version Support Matrix <https://docs.aws.amazon.com/sdkref/latest/guide/version-support-matrix.html>`__
More Resources
--------------
* `NOTICE <https://github.com/boto/boto3/blob/develop/NOTICE>`__
* `Changelog <https://github.com/boto/boto3/blob/develop/CHANGELOG.rst>`__
* `License <https://github.com/boto/boto3/blob/develop/LICENSE>`__

@ -0,0 +1,2 @@
boto3
Copyright 2013-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.

@ -0,0 +1,104 @@
boto3-1.34.39.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
boto3-1.34.39.dist-info/LICENSE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
boto3-1.34.39.dist-info/METADATA,sha256=ITvHDoNEs6a6UxC-4W-s8MDpPjPX7LEG1FCChPnVO3E,6620
boto3-1.34.39.dist-info/NOTICE,sha256=BPseYUhKeBDxugm7QrwByljJrzOSfXxaIVVuTE0cf6Q,83
boto3-1.34.39.dist-info/RECORD,,
boto3-1.34.39.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
boto3-1.34.39.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92
boto3-1.34.39.dist-info/top_level.txt,sha256=MP6_SI1GcPseXodd3Ykt5F_mCBsrUksiziLxjEZKGUU,6
boto3/__init__.py,sha256=RxCtP9O6cQq_SQeJ9D1fT0-RIb-UZTebzhvJHWudeFo,3420
boto3/__pycache__/__init__.cpython-310.pyc,,
boto3/__pycache__/compat.cpython-310.pyc,,
boto3/__pycache__/crt.cpython-310.pyc,,
boto3/__pycache__/exceptions.cpython-310.pyc,,
boto3/__pycache__/session.cpython-310.pyc,,
boto3/__pycache__/utils.cpython-310.pyc,,
boto3/compat.py,sha256=1T-LvBYqd0z-L9hI-soU7O7v-678tyWWR2pztF055u0,2887
boto3/crt.py,sha256=VFstUtHMZrZ6eHJJ-YdXb4vqfIOcHbv1l51fdeY5cS0,5407
boto3/data/cloudformation/2010-05-15/resources-1.json,sha256=5mFVKJVtbVoHyPdHSyNfZ5mpkgCAws5PhnveSu4qzdI,5110
boto3/data/cloudwatch/2010-08-01/resources-1.json,sha256=q4AgE8F4pbscd-2U3NYSGAzK55zpMyOQGr83JUxbZXI,11690
boto3/data/dynamodb/2012-08-10/resources-1.json,sha256=hBLa1Jt7bdT557U9A7UcSi8SCpONKzdbtDRTzjM1-Y0,3849
boto3/data/ec2/2014-10-01/resources-1.json,sha256=tMG1AMYP2ksnPWY6-3l8DB-EhKsSNtAO9YHhvHqBKu0,68469
boto3/data/ec2/2015-03-01/resources-1.json,sha256=tMG1AMYP2ksnPWY6-3l8DB-EhKsSNtAO9YHhvHqBKu0,68469
boto3/data/ec2/2015-04-15/resources-1.json,sha256=tMG1AMYP2ksnPWY6-3l8DB-EhKsSNtAO9YHhvHqBKu0,68469
boto3/data/ec2/2015-10-01/resources-1.json,sha256=SOfYX2c1KgvnxMO2FCdJpV42rJWNMwVhlFAXhvUPTzA,76564
boto3/data/ec2/2016-04-01/resources-1.json,sha256=SOfYX2c1KgvnxMO2FCdJpV42rJWNMwVhlFAXhvUPTzA,76564
boto3/data/ec2/2016-09-15/resources-1.json,sha256=SOfYX2c1KgvnxMO2FCdJpV42rJWNMwVhlFAXhvUPTzA,76564
boto3/data/ec2/2016-11-15/resources-1.json,sha256=vx7YiL-sUvBFeo4SZ81G7Qa2Hy-y6xY4z2YlSx7_wEw,76922
boto3/data/glacier/2012-06-01/resources-1.json,sha256=GT5qWQLGeXtrHgTDNG23Mrpyweg6O0Udgd139BuNTVs,19940
boto3/data/iam/2010-05-08/resources-1.json,sha256=PsOT9yBqSJtluBFHCVRsg6k6Ly2VkSYODnYxSl0DVOc,50357
boto3/data/opsworks/2013-02-18/resources-1.json,sha256=Y6ygEyegsbYA1gGZn-Ad2yuDd3jUCOt2UKrW_b2YBeM,4136
boto3/data/s3/2006-03-01/resources-1.json,sha256=VeKALhMRqv7fyDHMLOM5_RzXUEuDdg_n6OIRi3sdB-o,37204
boto3/data/sns/2010-03-31/resources-1.json,sha256=7zmKQhafgsRDu4U1yiw3NXHz-zJhHKrOmtuoYlxQP-s,9091
boto3/data/sqs/2012-11-05/resources-1.json,sha256=LRIIr5BId3UDeuBfLn-vRiWsSZCM9_ynqdxF8uzHgy8,6545
boto3/docs/__init__.py,sha256=ncXQfWgitU2kFSghqy2lezeeW1RneKZ-3wcsvEddsr0,1845
boto3/docs/__pycache__/__init__.cpython-310.pyc,,
boto3/docs/__pycache__/action.cpython-310.pyc,,
boto3/docs/__pycache__/attr.cpython-310.pyc,,
boto3/docs/__pycache__/base.cpython-310.pyc,,
boto3/docs/__pycache__/client.cpython-310.pyc,,
boto3/docs/__pycache__/collection.cpython-310.pyc,,
boto3/docs/__pycache__/docstring.cpython-310.pyc,,
boto3/docs/__pycache__/method.cpython-310.pyc,,
boto3/docs/__pycache__/resource.cpython-310.pyc,,
boto3/docs/__pycache__/service.cpython-310.pyc,,
boto3/docs/__pycache__/subresource.cpython-310.pyc,,
boto3/docs/__pycache__/utils.cpython-310.pyc,,
boto3/docs/__pycache__/waiter.cpython-310.pyc,,
boto3/docs/action.py,sha256=5ZQ2C9vIZdk8grFlnej-cwpVoNz0drcMiirKzqHczck,8178
boto3/docs/attr.py,sha256=BnG3tR1KKQvvY58aeJiWQ5W5DiMnJ_9jUjmG6tDbFiU,2500
boto3/docs/base.py,sha256=nOrQSCeUSIZPkn-I59o7CfjEthgdkpCt_rXtE9zQnXc,2103
boto3/docs/client.py,sha256=RpCngTolE4OtGIPvvJvkw8FJCqh5-7b-Q0QN5mVE7to,1078
boto3/docs/collection.py,sha256=pWO9I9LTMyhyYyCT_alrO4hZpqNI1228IwABotYTqBU,11679
boto3/docs/docstring.py,sha256=oPugaubdAXY6aNa-kXGI51lP1xE2s4AnfTsLhibf7-E,2511
boto3/docs/method.py,sha256=kJ3UJS2JBSt6SB_3TsEf3lxcjda5TAAfmocrLmxtSLc,2733
boto3/docs/resource.py,sha256=HBFq7c-tio19Da2Wb60j99EcW-I9M5-_C-9IjTuWrok,15376
boto3/docs/service.py,sha256=bCd2LPfZOeTkDOKggTyXJYXXPkuYUy91x5KYyqPPQnE,8544
boto3/docs/subresource.py,sha256=W19brjJjeW55ssyYhCnFaZICfp2LjOoC4BP_jW2ViC8,5864
boto3/docs/utils.py,sha256=H0UeVvmVbYBZ6F-CVEUxVggLMBOIoA5q8y8hxBFnRKE,5436
boto3/docs/waiter.py,sha256=xfnXtbMTOCyNG9vTNZW7Alsy77ZXuJCFcQcq0sNtg8Q,5175
boto3/dynamodb/__init__.py,sha256=GkSq-WxXWfVHu1SEcMrlJbzkfw9ACgF3UdCL6fPpTmY,562
boto3/dynamodb/__pycache__/__init__.cpython-310.pyc,,
boto3/dynamodb/__pycache__/conditions.cpython-310.pyc,,
boto3/dynamodb/__pycache__/table.cpython-310.pyc,,
boto3/dynamodb/__pycache__/transform.cpython-310.pyc,,
boto3/dynamodb/__pycache__/types.cpython-310.pyc,,
boto3/dynamodb/conditions.py,sha256=sjkd0kIqFP_h8aUvysZQel0zts5HF22ogqKiv0t0KRw,15045
boto3/dynamodb/table.py,sha256=us79dxZSQSno8gsUoAdQyzc2oBJL2riUpN6RGc8vQk8,6343
boto3/dynamodb/transform.py,sha256=JnW5ZzPIfxEcDszSvXKUZmp_1rw445tsddS3FG--JwA,12909
boto3/dynamodb/types.py,sha256=ch0vIKaAYexjL42S_OJWyvjWMcb0UbNrmkKGcz76O3c,9541
boto3/ec2/__init__.py,sha256=GkSq-WxXWfVHu1SEcMrlJbzkfw9ACgF3UdCL6fPpTmY,562
boto3/ec2/__pycache__/__init__.cpython-310.pyc,,
boto3/ec2/__pycache__/createtags.cpython-310.pyc,,
boto3/ec2/__pycache__/deletetags.cpython-310.pyc,,
boto3/ec2/createtags.py,sha256=pUPJOYn7m0Jcch9UL-DEVGgbQHoyAemECPBhzyBx28c,1577
boto3/ec2/deletetags.py,sha256=KaYcqSt8FFM_TW0g0pZ14qDjVnmRCPV0sMe6DprEtvo,1217
boto3/examples/cloudfront.rst,sha256=K-sBWZxoLjABCZHrqAZs57cYefwPmDir03pm6PE_mh4,1390
boto3/examples/s3.rst,sha256=jCfgEDfpw08nFtCizCN2OGg15zQRkx3DiJXZUfqhE2s,5486
boto3/exceptions.py,sha256=i13QpGxoFizxAGCzA2qmF9ldbI5IfBpn37DH75ddRF8,4127
boto3/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
boto3/resources/__pycache__/__init__.cpython-310.pyc,,
boto3/resources/__pycache__/action.cpython-310.pyc,,
boto3/resources/__pycache__/base.cpython-310.pyc,,
boto3/resources/__pycache__/collection.cpython-310.pyc,,
boto3/resources/__pycache__/factory.cpython-310.pyc,,
boto3/resources/__pycache__/model.cpython-310.pyc,,
boto3/resources/__pycache__/params.cpython-310.pyc,,
boto3/resources/__pycache__/response.cpython-310.pyc,,
boto3/resources/action.py,sha256=vPfVHVgXiGqhwpgRSCC7lSsY3vGjgsSiYhXa14CMAqw,9600
boto3/resources/base.py,sha256=Nf5Anssquo3urPDyWLAN8di379z5oafjwzl3gD9WbsI,5044
boto3/resources/collection.py,sha256=bSV0353zcTRLEPws2qqMFd2Xure8I8LgU-IDR-TM3sI,19242
boto3/resources/factory.py,sha256=iXV5l7UZePNIfkkUMgUNC0tIdJhxr_65m9KYdwIOfKA,22708
boto3/resources/model.py,sha256=3mCNSvnmCKPzjK-hW4yEv0PjKYb0hxBsAE9nopY-3bU,20394
boto3/resources/params.py,sha256=i6KAjOzjzou7ouViYbRZCz0CwqB6fA_6gOJFDIruTV8,6112
boto3/resources/response.py,sha256=aC1AZuO08qtb1psJtbrc5Na32AQ9WI-Il4DpVxsUtXs,11694
boto3/s3/__init__.py,sha256=GkSq-WxXWfVHu1SEcMrlJbzkfw9ACgF3UdCL6fPpTmY,562
boto3/s3/__pycache__/__init__.cpython-310.pyc,,
boto3/s3/__pycache__/constants.cpython-310.pyc,,
boto3/s3/__pycache__/inject.cpython-310.pyc,,
boto3/s3/__pycache__/transfer.cpython-310.pyc,,
boto3/s3/constants.py,sha256=ZaYknNwqGwsJEGkL92GXaBs9kjfRbyCDFt89wei8t7E,690
boto3/s3/inject.py,sha256=t1XiGqUIB_BNtJSiKgqu1hfzhfCntWzmUSxWgCPv4bU,28205
boto3/s3/transfer.py,sha256=FPQQ-ov8foEmA4DY6CYjitY7BchyGSzCpfBRPPhNj-0,15928
boto3/session.py,sha256=ITqrFauYJ74IfZrpPS411rLaKNNVQKgCe388fpnUV-0,20758
boto3/utils.py,sha256=dBw0Eu23TOhDsP1Lkrp4uOVMn5DS8s0kRGwVRiCD_KM,3141

@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.0)
Root-Is-Purelib: true
Tag: py3-none-any

@ -0,0 +1,111 @@
# 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 boto3.compat import _warn_deprecated_python
from boto3.session import Session
__author__ = 'Amazon Web Services'
__version__ = '1.34.39'
# 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()
_warn_deprecated_python()
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.
# https://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())

@ -0,0 +1,82 @@
# Copyright 2015 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 sys
import os
import errno
import socket
import warnings
from boto3.exceptions import PythonDeprecationWarning
# In python3, socket.error is OSError, which is too general
# for what we want (i.e FileNotFoundError is a subclass of OSError).
# In py3 all the socket related errors are in a newly created
# ConnectionError
SOCKET_ERROR = ConnectionError
import collections.abc as collections_abc
if sys.platform.startswith('win'):
def rename_file(current_filename, new_filename):
try:
os.remove(new_filename)
except OSError as e:
if not e.errno == errno.ENOENT:
# We only want to a ignore trying to remove
# a file that does not exist. If it fails
# for any other reason we should be propagating
# that exception.
raise
os.rename(current_filename, new_filename)
else:
rename_file = os.rename
def filter_python_deprecation_warnings():
"""
Invoking this filter acknowledges your runtime will soon be deprecated
at which time you will stop receiving all updates to your client.
"""
warnings.filterwarnings(
'ignore',
message=".*Boto3 will no longer support Python.*",
category=PythonDeprecationWarning,
module=r".*boto3\.compat"
)
def _warn_deprecated_python():
"""Use this template for future deprecation campaigns as needed."""
py_37_params = {
'date': 'December 13, 2023',
'blog_link': (
'https://aws.amazon.com/blogs/developer/'
'python-support-policy-updates-for-aws-sdks-and-tools/'
)
}
deprecated_versions = {
# Example template for future deprecations
(3, 7): py_37_params,
}
py_version = sys.version_info[:2]
if py_version in deprecated_versions:
params = deprecated_versions[py_version]
warning = (
"Boto3 will no longer support Python {}.{} "
"starting {}. To continue receiving service updates, "
"bug fixes, and security updates please upgrade to Python 3.8 or "
"later. More information can be found here: {}"
).format(py_version[0], py_version[1], params['date'], params['blog_link'])
warnings.warn(warning, PythonDeprecationWarning)

@ -0,0 +1,167 @@
# Copyright 2023 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.
"""
This file contains private functionality for interacting with the AWS
Common Runtime library (awscrt) in boto3.
All code contained within this file is for internal usage within this
project and is not intended for external consumption. All interfaces
contained within are subject to abrupt breaking changes.
"""
import threading
import botocore.exceptions
from botocore.session import Session
from s3transfer.crt import (
BotocoreCRTCredentialsWrapper,
BotocoreCRTRequestSerializer,
CRTTransferManager,
acquire_crt_s3_process_lock,
create_s3_crt_client,
)
# Singletons for CRT-backed transfers
CRT_S3_CLIENT = None
BOTOCORE_CRT_SERIALIZER = None
CLIENT_CREATION_LOCK = threading.Lock()
PROCESS_LOCK_NAME = 'boto3'
def _create_crt_client(session, config, region_name, cred_provider):
"""Create a CRT S3 Client for file transfer.
Instantiating many of these may lead to degraded performance or
system resource exhaustion.
"""
create_crt_client_kwargs = {
'region': region_name,
'use_ssl': True,
'crt_credentials_provider': cred_provider,
}
return create_s3_crt_client(**create_crt_client_kwargs)
def _create_crt_request_serializer(session, region_name):
return BotocoreCRTRequestSerializer(
session, {'region_name': region_name, 'endpoint_url': None}
)
def _create_crt_s3_client(
session, config, region_name, credentials, lock, **kwargs
):
"""Create boto3 wrapper class to manage crt lock reference and S3 client."""
cred_wrapper = BotocoreCRTCredentialsWrapper(credentials)
cred_provider = cred_wrapper.to_crt_credentials_provider()
return CRTS3Client(
_create_crt_client(session, config, region_name, cred_provider),
lock,
region_name,
cred_wrapper,
)
def _initialize_crt_transfer_primatives(client, config):
lock = acquire_crt_s3_process_lock(PROCESS_LOCK_NAME)
if lock is None:
# If we're unable to acquire the lock, we cannot
# use the CRT in this process and should default to
# the classic s3transfer manager.
return None, None
session = Session()
region_name = client.meta.region_name
credentials = client._get_credentials()
serializer = _create_crt_request_serializer(session, region_name)
s3_client = _create_crt_s3_client(
session, config, region_name, credentials, lock
)
return serializer, s3_client
def get_crt_s3_client(client, config):
global CRT_S3_CLIENT
global BOTOCORE_CRT_SERIALIZER
with CLIENT_CREATION_LOCK:
if CRT_S3_CLIENT is None:
serializer, s3_client = _initialize_crt_transfer_primatives(
client, config
)
BOTOCORE_CRT_SERIALIZER = serializer
CRT_S3_CLIENT = s3_client
return CRT_S3_CLIENT
class CRTS3Client:
"""
This wrapper keeps track of our underlying CRT client, the lock used to
acquire it and the region we've used to instantiate the client.
Due to limitations in the existing CRT interfaces, we can only make calls
in a single region and does not support redirects. We track the region to
ensure we don't use the CRT client when a successful request cannot be made.
"""
def __init__(self, crt_client, process_lock, region, cred_provider):
self.crt_client = crt_client
self.process_lock = process_lock
self.region = region
self.cred_provider = cred_provider
def is_crt_compatible_request(client, crt_s3_client):
"""
Boto3 client must use same signing region and credentials
as the CRT_S3_CLIENT singleton. Otherwise fallback to classic.
"""
if crt_s3_client is None:
return False
boto3_creds = client._get_credentials()
if boto3_creds is None:
return False
is_same_identity = compare_identity(
boto3_creds.get_frozen_credentials(), crt_s3_client.cred_provider
)
is_same_region = client.meta.region_name == crt_s3_client.region
return is_same_region and is_same_identity
def compare_identity(boto3_creds, crt_s3_creds):
try:
crt_creds = crt_s3_creds()
except botocore.exceptions.NoCredentialsError:
return False
is_matching_identity = (
boto3_creds.access_key == crt_creds.access_key_id
and boto3_creds.secret_key == crt_creds.secret_access_key
and boto3_creds.token == crt_creds.session_token
)
return is_matching_identity
def create_crt_transfer_manager(client, config):
"""Create a CRTTransferManager for optimized data transfer."""
crt_s3_client = get_crt_s3_client(client, config)
if is_crt_compatible_request(client, crt_s3_client):
return CRTTransferManager(
crt_s3_client.crt_client, BOTOCORE_CRT_SERIALIZER
)
return None

@ -0,0 +1,195 @@
{
"service": {
"actions": {
"CreateStack": {
"request": { "operation": "CreateStack" },
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Name", "source": "requestParameter", "path": "StackName" }
]
}
}
},
"has": {
"Event": {
"resource": {
"type": "Event",
"identifiers": [
{ "target": "Id", "source": "input" }
]
}
},
"Stack": {
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Name", "source": "input" }
]
}
}
},
"hasMany": {
"Stacks": {
"request": { "operation": "DescribeStacks" },
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Name", "source": "response", "path": "Stacks[].StackName" }
],
"path": "Stacks[]"
}
}
}
},
"resources": {
"Event": {
"identifiers": [
{
"name": "Id",
"memberName": "EventId"
}
],
"shape": "StackEvent"
},
"Stack": {
"identifiers": [
{
"name": "Name",
"memberName": "StackName"
}
],
"shape": "Stack",
"load": {
"request": {
"operation": "DescribeStacks",
"params": [
{ "target": "StackName", "source": "identifier", "name": "Name" }
]
},
"path": "Stacks[0]"
},
"actions": {
"CancelUpdate": {
"request": {
"operation": "CancelUpdateStack",
"params": [
{ "target": "StackName", "source": "identifier", "name": "Name" }
]
}
},
"Delete": {
"request": {
"operation": "DeleteStack",
"params": [
{ "target": "StackName", "source": "identifier", "name": "Name" }
]
}
},
"Update": {
"request": {
"operation": "UpdateStack",
"params": [
{ "target": "StackName", "source": "identifier", "name": "Name" }
]
}
}
},
"has": {
"Resource": {
"resource": {
"type": "StackResource",
"identifiers": [
{ "target": "StackName", "source": "identifier", "name": "Name" },
{ "target": "LogicalId", "source": "input" }
]
}
}
},
"hasMany": {
"Events": {
"request": {
"operation": "DescribeStackEvents",
"params": [
{ "target": "StackName", "source": "identifier", "name": "Name" }
]
},
"resource": {
"type": "Event",
"identifiers": [
{ "target": "Id", "source": "response", "path": "StackEvents[].EventId" }
],
"path": "StackEvents[]"
}
},
"ResourceSummaries": {
"request": {
"operation": "ListStackResources",
"params": [
{ "target": "StackName", "source": "identifier", "name": "Name" }
]
},
"resource": {
"type": "StackResourceSummary",
"identifiers": [
{ "target": "LogicalId", "source": "response", "path": "StackResourceSummaries[].LogicalResourceId" },
{ "target": "StackName", "source": "requestParameter", "path": "StackName" }
],
"path": "StackResourceSummaries[]"
}
}
}
},
"StackResource": {
"identifiers": [
{ "name": "StackName" },
{
"name": "LogicalId",
"memberName": "LogicalResourceId"
}
],
"shape": "StackResourceDetail",
"load": {
"request": {
"operation": "DescribeStackResource",
"params": [
{ "target": "LogicalResourceId", "source": "identifier", "name": "LogicalId" },
{ "target": "StackName", "source": "identifier", "name": "StackName" }
]
},
"path": "StackResourceDetail"
},
"has": {
"Stack": {
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Name", "source": "identifier", "name": "StackName" }
]
}
}
}
},
"StackResourceSummary": {
"identifiers": [
{ "name": "StackName" },
{
"name": "LogicalId",
"memberName": "LogicalResourceId"
}
],
"shape": "StackResourceSummary",
"has": {
"Resource": {
"resource": {
"type": "StackResource",
"identifiers": [
{ "target": "LogicalId", "source": "identifier", "name": "LogicalId" },
{ "target": "StackName", "source": "identifier", "name": "StackName" }
]
}
}
}
}
}
}

@ -0,0 +1,334 @@
{
"service": {
"has": {
"Alarm": {
"resource": {
"type": "Alarm",
"identifiers": [
{
"target": "Name",
"source": "input"
}
]
}
},
"Metric": {
"resource": {
"type": "Metric",
"identifiers": [
{
"target": "Namespace",
"source": "input"
},
{
"target": "Name",
"source": "input"
}
]
}
}
},
"hasMany": {
"Alarms": {
"request": { "operation": "DescribeAlarms" },
"resource": {
"type": "Alarm",
"identifiers": [
{
"target": "Name",
"source": "response",
"path": "MetricAlarms[].AlarmName"
}
],
"path": "MetricAlarms[]"
}
},
"Metrics": {
"request": { "operation": "ListMetrics" },
"resource": {
"type": "Metric",
"identifiers": [
{
"target": "Namespace",
"source": "response",
"path": "Metrics[].Namespace"
},
{
"target": "Name",
"source": "response",
"path": "Metrics[].MetricName"
}
],
"path": "Metrics[]"
}
}
}
},
"resources": {
"Alarm": {
"identifiers": [
{
"name": "Name",
"memberName": "AlarmName"
}
],
"shape": "MetricAlarm",
"load": {
"request": {
"operation": "DescribeAlarms",
"params": [
{
"target": "AlarmNames[0]",
"source": "identifier",
"name": "Name"
}
]
},
"path": "MetricAlarms[0]"
},
"actions": {
"Delete": {
"request": {
"operation": "DeleteAlarms",
"params": [
{
"target": "AlarmNames[0]",
"source": "identifier",
"name": "Name"
}
]
}
},
"DescribeHistory": {
"request": {
"operation": "DescribeAlarmHistory",
"params": [
{
"target": "AlarmName",
"source": "identifier",
"name": "Name"
}
]
}
},
"DisableActions": {
"request": {
"operation": "DisableAlarmActions",
"params": [
{
"target": "AlarmNames[0]",
"source": "identifier",
"name": "Name"
}
]
}
},
"EnableActions": {
"request": {
"operation": "EnableAlarmActions",
"params": [
{
"target": "AlarmNames[0]",
"source": "identifier",
"name": "Name"
}
]
}
},
"SetState": {
"request": {
"operation": "SetAlarmState",
"params": [
{
"target": "AlarmName",
"source": "identifier",
"name": "Name"
}
]
}
}
},
"batchActions": {
"Delete": {
"request": {
"operation": "DeleteAlarms",
"params": [
{
"target": "AlarmNames[]",
"source": "identifier",
"name": "Name"
}
]
}
},
"DisableActions": {
"request": {
"operation": "DisableAlarmActions",
"params": [
{
"target": "AlarmNames[]",
"source": "identifier",
"name": "Name"
}
]
}
},
"EnableActions": {
"request": {
"operation": "EnableAlarmActions",
"params": [
{
"target": "AlarmNames[]",
"source": "identifier",
"name": "Name"
}
]
}
}
},
"has": {
"Metric": {
"resource": {
"type": "Metric",
"identifiers": [
{
"target": "Namespace",
"source": "data",
"path": "Namespace"
},
{
"target": "Name",
"source": "data",
"path": "MetricName"
}
]
}
}
}
},
"Metric": {
"identifiers": [
{
"name": "Namespace",
"memberName": "Namespace"
},
{
"name": "Name",
"memberName": "MetricName"
}
],
"shape": "Metric",
"load": {
"request": {
"operation": "ListMetrics",
"params": [
{
"target": "MetricName",
"source": "identifier",
"name": "Name"
},
{
"target": "Namespace",
"source": "identifier",
"name": "Namespace"
}
]
},
"path": "Metrics[0]"
},
"actions": {
"GetStatistics": {
"request": {
"operation": "GetMetricStatistics",
"params": [
{
"target": "Namespace",
"source": "identifier",
"name": "Namespace"
},
{
"target": "MetricName",
"source": "identifier",
"name": "Name"
}
]
}
},
"PutAlarm": {
"request": {
"operation": "PutMetricAlarm",
"params": [
{
"target": "Namespace",
"source": "identifier",
"name": "Namespace"
},
{
"target": "MetricName",
"source": "identifier",
"name": "Name"
}
]
},
"resource": {
"type": "Alarm",
"identifiers": [
{
"target": "Name",
"source": "requestParameter",
"path": "AlarmName"
}
]
}
},
"PutData": {
"request": {
"operation": "PutMetricData",
"params": [
{
"target": "Namespace",
"source": "identifier",
"name": "Namespace"
},
{
"target": "MetricData[].MetricName",
"source": "identifier",
"name": "Name"
}
]
}
}
},
"hasMany": {
"Alarms": {
"request": {
"operation": "DescribeAlarmsForMetric",
"params": [
{
"target": "Namespace",
"source": "identifier",
"name": "Namespace"
},
{
"target": "MetricName",
"source": "identifier",
"name": "Name"
}
]
},
"resource": {
"type": "Alarm",
"identifiers": [
{
"target": "Name",
"source": "response",
"path": "MetricAlarms[].AlarmName"
}
],
"path": "MetricAlarms[]"
}
}
}
}
}
}

@ -0,0 +1,150 @@
{
"service": {
"actions": {
"BatchGetItem": {
"request": { "operation": "BatchGetItem" }
},
"BatchWriteItem": {
"request": { "operation": "BatchWriteItem" }
},
"CreateTable": {
"request": { "operation": "CreateTable" },
"resource": {
"type": "Table",
"identifiers": [
{ "target": "Name", "source": "response", "path": "TableDescription.TableName" }
],
"path": "TableDescription"
}
}
},
"has": {
"Table": {
"resource": {
"type": "Table",
"identifiers": [
{ "target": "Name", "source": "input" }
]
}
}
},
"hasMany": {
"Tables": {
"request": { "operation": "ListTables" },
"resource": {
"type": "Table",
"identifiers": [
{ "target": "Name", "source": "response", "path": "TableNames[]" }
]
}
}
}
},
"resources": {
"Table": {
"identifiers": [
{
"name": "Name",
"memberName": "TableName"
}
],
"shape": "TableDescription",
"load": {
"request": {
"operation": "DescribeTable",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
},
"path": "Table"
},
"actions": {
"Delete": {
"request": {
"operation": "DeleteTable",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
},
"DeleteItem": {
"request": {
"operation": "DeleteItem",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
},
"GetItem": {
"request": {
"operation": "GetItem",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
},
"PutItem": {
"request": {
"operation": "PutItem",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
},
"Query": {
"request": {
"operation": "Query",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
},
"Scan": {
"request": {
"operation": "Scan",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
},
"Update": {
"request": {
"operation": "UpdateTable",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
},
"resource": {
"type": "Table",
"identifiers": [
{ "target": "Name", "source": "identifier", "name": "Name" }
],
"path": "TableDescription"
}
},
"UpdateItem": {
"request": {
"operation": "UpdateItem",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
}
},
"waiters":{
"Exists": {
"waiterName": "TableExists",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
},
"NotExists": {
"waiterName": "TableNotExists",
"params": [
{ "target": "TableName", "source": "identifier", "name": "Name" }
]
}
}
}
}
}

@ -0,0 +1,581 @@
{
"service": {
"actions": {
"CreateVault": {
"request": {
"operation": "CreateVault",
"params": [
{ "target": "accountId", "source": "string", "value": "-" }
]
},
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "requestParameter", "path": "accountId" },
{ "target": "Name", "source": "requestParameter", "path": "vaultName" }
]
}
}
},
"has": {
"Account": {
"resource": {
"type": "Account",
"identifiers": [
{ "target": "Id", "source": "input" }
]
}
}
},
"hasMany": {
"Vaults": {
"request": {
"operation": "ListVaults",
"params": [
{ "target": "accountId", "source": "string", "value": "-" }
]
},
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "requestParameter", "path": "accountId" },
{ "target": "Name", "source": "response", "path": "VaultList[].VaultName" }
],
"path": "VaultList[]"
}
}
}
},
"resources": {
"Account": {
"identifiers": [
{ "name": "Id" }
],
"actions": {
"CreateVault": {
"request": {
"operation": "CreateVault",
"params": [
{ "target": "accountId", "source": "identifier", "name": "Id" }
]
},
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "Id" },
{ "target": "Name", "source": "requestParameter", "path": "vaultName" }
]
}
}
},
"has": {
"Vault": {
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "Id" },
{ "target": "Name", "source": "input" }
]
}
}
},
"hasMany": {
"Vaults": {
"request": {
"operation": "ListVaults",
"params": [
{ "target": "accountId", "source": "identifier", "name": "Id" }
]
},
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "Id" },
{ "target": "Name", "source": "response", "path": "VaultList[].VaultName" }
],
"path": "VaultList[]"
}
}
}
},
"Archive": {
"identifiers": [
{ "name": "AccountId" },
{ "name": "VaultName" },
{ "name": "Id" }
],
"actions": {
"Delete": {
"request": {
"operation": "DeleteArchive",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "archiveId", "source": "identifier", "name": "Id" }
]
}
},
"InitiateArchiveRetrieval": {
"request": {
"operation": "InitiateJob",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "jobParameters.Type", "source": "string", "value": "archive-retrieval" },
{ "target": "jobParameters.ArchiveId", "source": "identifier", "name": "Id" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "Id", "source": "response", "path": "jobId" },
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "VaultName" }
]
}
}
},
"has": {
"Vault": {
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "Name", "source": "identifier", "name": "VaultName" }
]
}
}
}
},
"Job": {
"identifiers": [
{ "name": "AccountId" },
{ "name": "VaultName" },
{
"name": "Id",
"memberName": "JobId"
}
],
"shape": "GlacierJobDescription",
"load": {
"request": {
"operation": "DescribeJob",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "jobId", "source": "identifier", "name": "Id" }
]
},
"path": "@"
},
"actions": {
"GetOutput": {
"request": {
"operation": "GetJobOutput",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "jobId", "source": "identifier", "name": "Id" }
]
}
}
},
"has": {
"Vault": {
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "Name", "source": "identifier", "name": "VaultName" }
]
}
}
}
},
"MultipartUpload": {
"identifiers": [
{ "name": "AccountId" },
{ "name": "VaultName" },
{
"name": "Id",
"memberName": "MultipartUploadId"
}
],
"shape": "UploadListElement",
"actions": {
"Abort": {
"request": {
"operation": "AbortMultipartUpload",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "uploadId", "source": "identifier", "name": "Id" }
]
}
},
"Complete": {
"request": {
"operation": "CompleteMultipartUpload",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "uploadId", "source": "identifier", "name": "Id" }
]
}
},
"Parts": {
"request": {
"operation": "ListParts",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "uploadId", "source": "identifier", "name": "Id" }
]
}
},
"UploadPart": {
"request": {
"operation": "UploadMultipartPart",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" },
{ "target": "uploadId", "source": "identifier", "name": "Id" }
]
}
}
},
"has": {
"Vault": {
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "Name", "source": "identifier", "name": "VaultName" }
]
}
}
}
},
"Notification": {
"identifiers": [
{ "name": "AccountId" },
{ "name": "VaultName" }
],
"shape": "VaultNotificationConfig",
"load": {
"request": {
"operation": "GetVaultNotifications",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" }
]
},
"path": "vaultNotificationConfig"
},
"actions": {
"Delete": {
"request": {
"operation": "DeleteVaultNotifications",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" }
]
}
},
"Set": {
"request": {
"operation": "SetVaultNotifications",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "VaultName" }
]
}
}
},
"has": {
"Vault": {
"resource": {
"type": "Vault",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "Name", "source": "identifier", "name": "VaultName" }
]
}
}
}
},
"Vault": {
"identifiers": [
{ "name": "AccountId" },
{
"name": "Name",
"memberName": "VaultName"
}
],
"shape": "DescribeVaultOutput",
"load": {
"request": {
"operation": "DescribeVault",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
},
"path": "@"
},
"actions": {
"Create": {
"request": {
"operation": "CreateVault",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
}
},
"Delete": {
"request": {
"operation": "DeleteVault",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
}
},
"InitiateInventoryRetrieval": {
"request": {
"operation": "InitiateJob",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "jobParameters.Type", "source": "string", "value": "inventory-retrieval" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "Id", "source": "response", "path": "jobId" },
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" }
]
}
},
"InitiateMultipartUpload": {
"request": {
"operation": "InitiateMultipartUpload",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
},
"resource": {
"type": "MultipartUpload",
"identifiers": [
{ "target": "Id", "source": "response", "path": "uploadId" },
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" }
]
}
},
"UploadArchive": {
"request": {
"operation": "UploadArchive",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
},
"resource": {
"type": "Archive",
"identifiers": [
{ "target": "Id", "source": "response", "path": "archiveId" },
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" }
]
}
}
},
"has": {
"Account": {
"resource": {
"type": "Account",
"identifiers": [
{ "target": "Id", "source": "identifier", "name": "AccountId" }
]
}
},
"Archive": {
"resource": {
"type": "Archive",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "input" }
]
}
},
"Job": {
"resource": {
"type": "Job",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "input" }
]
}
},
"MultipartUpload": {
"resource": {
"type": "MultipartUpload",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "input" }
]
}
},
"Notification": {
"resource": {
"type": "Notification",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" }
]
}
}
},
"hasMany": {
"CompletedJobs": {
"request": {
"operation": "ListJobs",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "completed", "source": "string", "value": "true" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "JobList[].JobId" }
],
"path": "JobList[]"
}
},
"FailedJobs": {
"request": {
"operation": "ListJobs",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "statuscode", "source": "string", "value": "Failed" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "JobList[].JobId" }
],
"path": "JobList[]"
}
},
"Jobs": {
"request": {
"operation": "ListJobs",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "Name" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "JobList[].JobId" }
],
"path": "JobList[]"
}
},
"JobsInProgress": {
"request": {
"operation": "ListJobs",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "statuscode", "source": "string", "value": "InProgress" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "JobList[].JobId" }
],
"path": "JobList[]"
}
},
"MultipartUplaods": {
"request": {
"operation": "ListMultipartUploads",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
},
"resource": {
"type": "MultipartUpload",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "UploadsList[].MultipartUploadId" }
],
"path": "UploadsList[]"
}
},
"MultipartUploads": {
"request": {
"operation": "ListMultipartUploads",
"params": [
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "accountId", "source": "identifier", "name": "AccountId" }
]
},
"resource": {
"type": "MultipartUpload",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "UploadsList[].MultipartUploadId" }
],
"path": "UploadsList[]"
}
},
"SucceededJobs": {
"request": {
"operation": "ListJobs",
"params": [
{ "target": "accountId", "source": "identifier", "name": "AccountId" },
{ "target": "vaultName", "source": "identifier", "name": "Name" },
{ "target": "statuscode", "source": "string", "value": "Succeeded" }
]
},
"resource": {
"type": "Job",
"identifiers": [
{ "target": "AccountId", "source": "identifier", "name": "AccountId" },
{ "target": "VaultName", "source": "identifier", "name": "Name" },
{ "target": "Id", "source": "response", "path": "JobList[].JobId" }
],
"path": "JobList[]"
}
}
}
}
}
}

@ -0,0 +1,173 @@
{
"service": {
"actions": {
"CreateStack": {
"request": { "operation": "CreateStack" },
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Id", "source": "response", "path": "StackId" }
]
}
}
},
"has": {
"Layer": {
"resource": {
"type": "Layer",
"identifiers": [
{ "target": "Id", "source": "input" }
]
}
},
"Stack": {
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Id", "source": "input" }
]
}
}
},
"hasMany": {
"Stacks": {
"request": { "operation": "DescribeStacks" },
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Id", "source": "response", "path": "Stacks[].StackId" }
],
"path": "Stacks[]"
}
}
}
},
"resources": {
"Layer": {
"identifiers": [
{ "name": "Id" }
],
"shape": "Layer",
"load": {
"request": {
"operation": "DescribeLayers",
"params": [
{ "target": "LayerIds[]", "source": "identifier", "name": "Id" }
]
},
"path": "Layers[0]"
},
"actions": {
"Delete": {
"request": {
"operation": "DeleteLayer",
"params": [
{ "target": "LayerId", "source": "identifier", "name": "Id" }
]
}
}
},
"has": {
"Stack": {
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Id", "source": "data", "path": "StackId" }
]
}
}
}
},
"Stack": {
"identifiers": [
{ "name": "Id" }
],
"shape": "Stack",
"load": {
"request": {
"operation": "DescribeStacks",
"params": [
{ "target": "StackIds[]", "source": "identifier", "name": "Id" }
]
},
"path": "Stacks[0]"
},
"actions": {
"CreateLayer": {
"request": {
"operation": "CreateLayer",
"params": [
{ "target": "StackId", "source": "identifier", "name": "Id" }
]
},
"resource": {
"type": "Layer",
"identifiers": [
{ "target": "Id", "source": "response", "path": "LayerId" }
]
}
},
"Delete": {
"request": {
"operation": "DeleteStack",
"params": [
{ "target": "StackId", "source": "identifier", "name": "Id" }
]
}
}
},
"has": {
"Summary": {
"resource": {
"type": "StackSummary",
"identifiers": [
{ "target": "StackId", "source": "identifier", "name": "Id" }
]
}
}
},
"hasMany": {
"Layers": {
"request": {
"operation": "DescribeLayers",
"params": [
{ "target": "StackId", "source": "identifier", "name": "Id" }
]
},
"resource": {
"type": "Layer",
"identifiers": [
{ "target": "Id", "source": "response", "path": "Layers[].LayerId" }
],
"path": "Layers[]"
}
}
}
},
"StackSummary": {
"identifiers": [
{ "name": "StackId" }
],
"shape": "StackSummary",
"load": {
"request": {
"operation": "DescribeStackSummary",
"params": [
{ "target": "StackId", "source": "identifier", "name": "StackId" }
]
},
"path": "StackSummary"
},
"has": {
"Stack": {
"resource": {
"type": "Stack",
"identifiers": [
{ "target": "Id", "source": "identifier", "name": "StackId" }
]
}
}
}
}
}
}

@ -0,0 +1,327 @@
{
"service": {
"actions": {
"CreatePlatformApplication": {
"request": { "operation": "CreatePlatformApplication" },
"resource": {
"type": "PlatformApplication",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "PlatformApplicationArn" }
]
}
},
"CreateTopic": {
"request": { "operation": "CreateTopic" },
"resource": {
"type": "Topic",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "TopicArn" }
]
}
}
},
"has": {
"PlatformApplication": {
"resource": {
"type": "PlatformApplication",
"identifiers": [
{ "target": "Arn", "source": "input" }
]
}
},
"PlatformEndpoint": {
"resource": {
"type": "PlatformEndpoint",
"identifiers": [
{ "target": "Arn", "source": "input" }
]
}
},
"Subscription": {
"resource": {
"type": "Subscription",
"identifiers": [
{ "target": "Arn", "source": "input" }
]
}
},
"Topic": {
"resource": {
"type": "Topic",
"identifiers": [
{ "target": "Arn", "source": "input" }
]
}
}
},
"hasMany": {
"PlatformApplications": {
"request": { "operation": "ListPlatformApplications" },
"resource": {
"type": "PlatformApplication",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "PlatformApplications[].PlatformApplicationArn" }
]
}
},
"Subscriptions": {
"request": { "operation": "ListSubscriptions" },
"resource": {
"type": "Subscription",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "Subscriptions[].SubscriptionArn" }
]
}
},
"Topics": {
"request": { "operation": "ListTopics" },
"resource": {
"type": "Topic",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "Topics[].TopicArn" }
]
}
}
}
},
"resources": {
"PlatformApplication": {
"identifiers": [
{ "name": "Arn" }
],
"shape": "GetPlatformApplicationAttributesResponse",
"load": {
"request": {
"operation": "GetPlatformApplicationAttributes",
"params": [
{ "target": "PlatformApplicationArn", "source": "identifier", "name": "Arn" }
]
},
"path": "@"
},
"actions": {
"CreatePlatformEndpoint": {
"request": {
"operation": "CreatePlatformEndpoint",
"params": [
{ "target": "PlatformApplicationArn", "source": "identifier", "name": "Arn" }
]
},
"resource": {
"type": "PlatformEndpoint",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "EndpointArn" }
]
}
},
"Delete": {
"request": {
"operation": "DeletePlatformApplication",
"params": [
{ "target": "PlatformApplicationArn", "source": "identifier", "name": "Arn" }
]
}
},
"SetAttributes": {
"request": {
"operation": "SetPlatformApplicationAttributes",
"params": [
{ "target": "PlatformApplicationArn", "source": "identifier", "name": "Arn" }
]
}
}
},
"hasMany": {
"Endpoints": {
"request": {
"operation": "ListEndpointsByPlatformApplication",
"params": [
{ "target": "PlatformApplicationArn", "source": "identifier", "name": "Arn" }
]
},
"resource": {
"type": "PlatformEndpoint",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "Endpoints[].EndpointArn" }
]
}
}
}
},
"PlatformEndpoint": {
"identifiers": [
{ "name": "Arn" }
],
"shape": "GetEndpointAttributesResponse",
"load": {
"request": {
"operation": "GetEndpointAttributes",
"params": [
{ "target": "EndpointArn", "source": "identifier", "name": "Arn" }
]
},
"path": "@"
},
"actions": {
"Delete": {
"request": {
"operation": "DeleteEndpoint",
"params": [
{ "target": "EndpointArn", "source": "identifier", "name": "Arn" }
]
}
},
"Publish": {
"request": {
"operation": "Publish",
"params": [
{ "target": "TargetArn", "source": "identifier", "name": "Arn" }
]
}
},
"SetAttributes": {
"request": {
"operation": "SetEndpointAttributes",
"params": [
{ "target": "EndpointArn", "source": "identifier", "name": "Arn" }
]
}
}
}
},
"Subscription": {
"identifiers": [
{ "name": "Arn" }
],
"shape": "GetSubscriptionAttributesResponse",
"load": {
"request": {
"operation": "GetSubscriptionAttributes",
"params": [
{ "target": "SubscriptionArn", "source": "identifier", "name": "Arn" }
]
},
"path": "@"
},
"actions": {
"Delete": {
"request": {
"operation": "Unsubscribe",
"params": [
{ "target": "SubscriptionArn", "source": "identifier", "name": "Arn" }
]
}
},
"SetAttributes": {
"request": {
"operation": "SetSubscriptionAttributes",
"params": [
{ "target": "SubscriptionArn", "source": "identifier", "name": "Arn" }
]
}
}
}
},
"Topic": {
"identifiers": [
{ "name": "Arn" }
],
"shape": "GetTopicAttributesResponse",
"load": {
"request": {
"operation": "GetTopicAttributes",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
},
"path": "@"
},
"actions": {
"AddPermission": {
"request": {
"operation": "AddPermission",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
}
},
"ConfirmSubscription": {
"request": {
"operation": "ConfirmSubscription",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
},
"resource": {
"type": "Subscription",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "SubscriptionArn" }
]
}
},
"Delete": {
"request": {
"operation": "DeleteTopic",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
}
},
"Publish": {
"request": {
"operation": "Publish",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
}
},
"RemovePermission": {
"request": {
"operation": "RemovePermission",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
}
},
"SetAttributes": {
"request": {
"operation": "SetTopicAttributes",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
}
},
"Subscribe": {
"request": {
"operation": "Subscribe",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
},
"resource": {
"type": "Subscription",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "SubscriptionArn" }
]
}
}
},
"hasMany": {
"Subscriptions": {
"request": {
"operation": "ListSubscriptionsByTopic",
"params": [
{ "target": "TopicArn", "source": "identifier", "name": "Arn" }
]
},
"resource": {
"type": "Subscription",
"identifiers": [
{ "target": "Arn", "source": "response", "path": "Subscriptions[].SubscriptionArn" }
]
}
}
}
}
}
}

@ -0,0 +1,232 @@
{
"service": {
"actions": {
"CreateQueue": {
"request": { "operation": "CreateQueue" },
"resource": {
"type": "Queue",
"identifiers": [
{ "target": "Url", "source": "response", "path": "QueueUrl" }
]
}
},
"GetQueueByName": {
"request": { "operation": "GetQueueUrl" },
"resource": {
"type": "Queue",
"identifiers": [
{ "target": "Url", "source": "response", "path": "QueueUrl" }
]
}
}
},
"has": {
"Queue": {
"resource": {
"type": "Queue",
"identifiers": [
{ "target": "Url", "source": "input" }
]
}
}
},
"hasMany": {
"Queues": {
"request": { "operation": "ListQueues" },
"resource": {
"type": "Queue",
"identifiers": [
{ "target": "Url", "source": "response", "path": "QueueUrls[]" }
]
}
}
}
},
"resources": {
"Message": {
"identifiers": [
{ "name": "QueueUrl" },
{
"name": "ReceiptHandle",
"memberName": "ReceiptHandle"
}
],
"shape": "Message",
"actions": {
"ChangeVisibility": {
"request": {
"operation": "ChangeMessageVisibility",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "QueueUrl" },
{ "target": "ReceiptHandle", "source": "identifier", "name": "ReceiptHandle" }
]
}
},
"Delete": {
"request": {
"operation": "DeleteMessage",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "QueueUrl" },
{ "target": "ReceiptHandle", "source": "identifier", "name": "ReceiptHandle" }
]
}
}
},
"batchActions": {
"Delete": {
"request": {
"operation": "DeleteMessageBatch",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "QueueUrl" },
{ "target": "Entries[*].Id", "source": "data", "path": "MessageId" },
{ "target": "Entries[*].ReceiptHandle", "source": "identifier", "name": "ReceiptHandle" }
]
}
}
},
"has": {
"Queue": {
"resource": {
"type": "Queue",
"identifiers": [
{ "target": "Url", "source": "identifier", "name": "QueueUrl" }
]
}
}
}
},
"Queue": {
"identifiers": [
{ "name": "Url" }
],
"shape": "GetQueueAttributesResult",
"load": {
"request": {
"operation": "GetQueueAttributes",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" },
{ "target": "AttributeNames[]", "source": "string", "value": "All" }
]
},
"path": "@"
},
"actions": {
"AddPermission": {
"request": {
"operation": "AddPermission",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"ChangeMessageVisibilityBatch": {
"request": {
"operation": "ChangeMessageVisibilityBatch",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"Delete": {
"request": {
"operation": "DeleteQueue",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"DeleteMessages": {
"request": {
"operation": "DeleteMessageBatch",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"Purge": {
"request": {
"operation": "PurgeQueue",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"ReceiveMessages": {
"request": {
"operation": "ReceiveMessage",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
},
"resource": {
"type": "Message",
"identifiers": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" },
{ "target": "ReceiptHandle", "source": "response", "path": "Messages[].ReceiptHandle" }
],
"path": "Messages[]"
}
},
"RemovePermission": {
"request": {
"operation": "RemovePermission",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"SendMessage": {
"request": {
"operation": "SendMessage",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"SendMessages": {
"request": {
"operation": "SendMessageBatch",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
},
"SetAttributes": {
"request": {
"operation": "SetQueueAttributes",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
}
}
},
"has": {
"Message": {
"resource": {
"type": "Message",
"identifiers": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" },
{ "target": "ReceiptHandle", "source": "input" }
]
}
}
},
"hasMany": {
"DeadLetterSourceQueues": {
"request": {
"operation": "ListDeadLetterSourceQueues",
"params": [
{ "target": "QueueUrl", "source": "identifier", "name": "Url" }
]
},
"resource": {
"type": "Queue",
"identifiers": [
{ "target": "Url", "source": "response", "path": "queueUrls[]" }
]
}
}
}
}
}
}

@ -0,0 +1,51 @@
# Copyright 2015 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 os
from botocore.docs import DEPRECATED_SERVICE_NAMES
from boto3.docs.service import ServiceDocumenter
def generate_docs(root_dir, session):
"""Generates the reference documentation for botocore
This will go through every available AWS service and output ReSTructured
text files documenting each service.
:param root_dir: The directory to write the reference files to. Each
service's reference documentation is loacated at
root_dir/reference/services/service-name.rst
:param session: The boto3 session
"""
services_doc_path = os.path.join(root_dir, 'reference', 'services')
if not os.path.exists(services_doc_path):
os.makedirs(services_doc_path)
# Prevents deprecated service names from being generated in docs.
available_services = [
service
for service in session.get_available_services()
if service not in DEPRECATED_SERVICE_NAMES
]
for service_name in available_services:
docs = ServiceDocumenter(
service_name, session, services_doc_path
).document_service()
service_doc_path = os.path.join(
services_doc_path, service_name + '.rst'
)
with open(service_doc_path, 'wb') as f:
f.write(docs)

@ -0,0 +1,217 @@
# Copyright 2015 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 os
from botocore import xform_name
from botocore.docs.bcdoc.restdoc import DocumentStructure
from botocore.docs.method import (
document_custom_method,
document_model_driven_method,
)
from botocore.model import OperationModel
from botocore.utils import get_service_module_name
from boto3.docs.base import NestedDocumenter
from boto3.docs.method import document_model_driven_resource_method
from boto3.docs.utils import (
add_resource_type_overview,
get_resource_ignore_params,
get_resource_public_actions,
)
PUT_DATA_WARNING_MESSAGE = """
.. warning::
It is recommended to use the :py:meth:`put_metric_data`
:doc:`client method <../../cloudwatch/client/put_metric_data>`
instead. If you would still like to use this resource method,
please make sure that ``MetricData[].MetricName`` is equal to
the metric resource's ``name`` attribute.
"""
WARNING_MESSAGES = {
"Metric": {"put_data": PUT_DATA_WARNING_MESSAGE},
}
IGNORE_PARAMS = {"Metric": {"put_data": ["Namespace"]}}
class ActionDocumenter(NestedDocumenter):
def document_actions(self, section):
modeled_actions_list = self._resource_model.actions
modeled_actions = {}
for modeled_action in modeled_actions_list:
modeled_actions[modeled_action.name] = modeled_action
resource_actions = get_resource_public_actions(
self._resource.__class__
)
self.member_map['actions'] = sorted(resource_actions)
add_resource_type_overview(
section=section,
resource_type='Actions',
description=(
'Actions call operations on resources. They may '
'automatically handle the passing in of arguments set '
'from identifiers and some attributes.'
),
intro_link='actions_intro',
)
resource_warnings = WARNING_MESSAGES.get(self._resource_name, {})
for action_name in sorted(resource_actions):
# Create a new DocumentStructure for each action and add contents.
action_doc = DocumentStructure(action_name, target='html')
breadcrumb_section = action_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Action / {action_name}')
action_doc.add_title_section(action_name)
warning_message = resource_warnings.get(action_name)
if warning_message is not None:
action_doc.add_new_section("warning").write(warning_message)
action_section = action_doc.add_new_section(
action_name,
context={'qualifier': f'{self.class_name}.'},
)
if action_name in ['load', 'reload'] and self._resource_model.load:
document_load_reload_action(
section=action_section,
action_name=action_name,
resource_name=self._resource_name,
event_emitter=self._resource.meta.client.meta.events,
load_model=self._resource_model.load,
service_model=self._service_model,
)
elif action_name in modeled_actions:
document_action(
section=action_section,
resource_name=self._resource_name,
event_emitter=self._resource.meta.client.meta.events,
action_model=modeled_actions[action_name],
service_model=self._service_model,
)
else:
document_custom_method(
action_section, action_name, resource_actions[action_name]
)
# Write actions in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<action_name>.rst
actions_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
action_doc.write_to_file(actions_dir_path, action_name)
def document_action(
section,
resource_name,
event_emitter,
action_model,
service_model,
include_signature=True,
):
"""Documents a resource action
:param section: The section to write to
:param resource_name: The name of the resource
:param event_emitter: The event emitter to use to emit events
:param action_model: The model of the action
:param service_model: The model of the service
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
operation_model = service_model.operation_model(
action_model.request.operation
)
ignore_params = IGNORE_PARAMS.get(resource_name, {}).get(
action_model.name,
get_resource_ignore_params(action_model.request.params),
)
example_return_value = 'response'
if action_model.resource:
example_return_value = xform_name(action_model.resource.type)
example_resource_name = xform_name(resource_name)
if service_model.service_name == resource_name:
example_resource_name = resource_name
example_prefix = '{} = {}.{}'.format(
example_return_value, example_resource_name, action_model.name
)
full_action_name = (
f"{section.context.get('qualifier', '')}{action_model.name}"
)
document_model_driven_resource_method(
section=section,
method_name=full_action_name,
operation_model=operation_model,
event_emitter=event_emitter,
method_description=operation_model.documentation,
example_prefix=example_prefix,
exclude_input=ignore_params,
resource_action_model=action_model,
include_signature=include_signature,
)
def document_load_reload_action(
section,
action_name,
resource_name,
event_emitter,
load_model,
service_model,
include_signature=True,
):
"""Documents the resource load action
:param section: The section to write to
:param action_name: The name of the loading action should be load or reload
:param resource_name: The name of the resource
:param event_emitter: The event emitter to use to emit events
:param load_model: The model of the load action
:param service_model: The model of the service
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
description = (
'Calls :py:meth:`{}.Client.{}` to update the attributes of the '
'{} resource. Note that the load and reload methods are '
'the same method and can be used interchangeably.'.format(
get_service_module_name(service_model),
xform_name(load_model.request.operation),
resource_name,
)
)
example_resource_name = xform_name(resource_name)
if service_model.service_name == resource_name:
example_resource_name = resource_name
example_prefix = f'{example_resource_name}.{action_name}'
full_action_name = f"{section.context.get('qualifier', '')}{action_name}"
document_model_driven_method(
section=section,
method_name=full_action_name,
operation_model=OperationModel({}, service_model),
event_emitter=event_emitter,
method_description=description,
example_prefix=example_prefix,
include_signature=include_signature,
)

@ -0,0 +1,72 @@
# Copyright 2015 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.
from botocore.docs.params import ResponseParamsDocumenter
from boto3.docs.utils import get_identifier_description
class ResourceShapeDocumenter(ResponseParamsDocumenter):
EVENT_NAME = 'resource-shape'
def document_attribute(
section,
service_name,
resource_name,
attr_name,
event_emitter,
attr_model,
include_signature=True,
):
if include_signature:
full_attr_name = f"{section.context.get('qualifier', '')}{attr_name}"
section.style.start_sphinx_py_attr(full_attr_name)
# Note that an attribute may have one, may have many, or may have no
# operations that back the resource's shape. So we just set the
# operation_name to the resource name if we ever to hook in and modify
# a particular attribute.
ResourceShapeDocumenter(
service_name=service_name,
operation_name=resource_name,
event_emitter=event_emitter,
).document_params(section=section, shape=attr_model)
def document_identifier(
section,
resource_name,
identifier_model,
include_signature=True,
):
if include_signature:
full_identifier_name = (
f"{section.context.get('qualifier', '')}{identifier_model.name}"
)
section.style.start_sphinx_py_attr(full_identifier_name)
description = get_identifier_description(
resource_name, identifier_model.name
)
section.write(f'*(string)* {description}')
def document_reference(section, reference_model, include_signature=True):
if include_signature:
full_reference_name = (
f"{section.context.get('qualifier', '')}{reference_model.name}"
)
section.style.start_sphinx_py_attr(full_reference_name)
reference_type = f'(:py:class:`{reference_model.resource.type}`) '
section.write(reference_type)
section.include_doc_string(
f'The related {reference_model.name} if set, otherwise ``None``.'
)

@ -0,0 +1,51 @@
# Copyright 2015 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.
from botocore.compat import OrderedDict
class BaseDocumenter:
def __init__(self, resource):
self._resource = resource
self._client = self._resource.meta.client
self._resource_model = self._resource.meta.resource_model
self._service_model = self._client.meta.service_model
self._resource_name = self._resource.meta.resource_model.name
self._service_name = self._service_model.service_name
self._service_docs_name = self._client.__class__.__name__
self.member_map = OrderedDict()
self.represents_service_resource = (
self._service_name == self._resource_name
)
self._resource_class_name = self._resource_name
if self._resource_name == self._service_name:
self._resource_class_name = 'ServiceResource'
@property
def class_name(self):
return f'{self._service_docs_name}.{self._resource_name}'
class NestedDocumenter(BaseDocumenter):
def __init__(self, resource, root_docs_path):
super().__init__(resource)
self._root_docs_path = root_docs_path
self._resource_sub_path = self._resource_name.lower()
if self._resource_name == self._service_name:
self._resource_sub_path = 'service-resource'
@property
def class_name(self):
resource_class_name = self._resource_name
if self._resource_name == self._service_name:
resource_class_name = 'ServiceResource'
return f'{self._service_docs_name}.{resource_class_name}'

@ -0,0 +1,28 @@
# Copyright 2015 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.
from botocore.docs.client import ClientDocumenter
class Boto3ClientDocumenter(ClientDocumenter):
def _add_client_creation_example(self, section):
section.style.start_codeblock()
section.style.new_line()
section.write('import boto3')
section.style.new_line()
section.style.new_line()
section.write(
'client = boto3.client(\'{service}\')'.format(
service=self._service_name
)
)
section.style.end_codeblock()

@ -0,0 +1,312 @@
# Copyright 2015 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 os
from botocore import xform_name
from botocore.docs.bcdoc.restdoc import DocumentStructure
from botocore.docs.method import get_instance_public_methods
from botocore.docs.utils import DocumentedShape
from boto3.docs.base import NestedDocumenter
from boto3.docs.method import document_model_driven_resource_method
from boto3.docs.utils import (
add_resource_type_overview,
get_resource_ignore_params,
)
class CollectionDocumenter(NestedDocumenter):
def document_collections(self, section):
collections = self._resource.meta.resource_model.collections
collections_list = []
add_resource_type_overview(
section=section,
resource_type='Collections',
description=(
'Collections provide an interface to iterate over and '
'manipulate groups of resources. '
),
intro_link='guide_collections',
)
self.member_map['collections'] = collections_list
for collection in collections:
collections_list.append(collection.name)
# Create a new DocumentStructure for each collection and add contents.
collection_doc = DocumentStructure(collection.name, target='html')
breadcrumb_section = collection_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Collection / {collection.name}')
collection_doc.add_title_section(collection.name)
collection_section = collection_doc.add_new_section(
collection.name,
context={'qualifier': f'{self.class_name}.'},
)
self._document_collection(collection_section, collection)
# Write collections in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<collection_name>.rst
collections_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
collection_doc.write_to_file(collections_dir_path, collection.name)
def _document_collection(self, section, collection):
methods = get_instance_public_methods(
getattr(self._resource, collection.name)
)
document_collection_object(section, collection)
batch_actions = {}
for batch_action in collection.batch_actions:
batch_actions[batch_action.name] = batch_action
for method in sorted(methods):
method_section = section.add_new_section(method)
if method in batch_actions:
document_batch_action(
section=method_section,
resource_name=self._resource_name,
event_emitter=self._resource.meta.client.meta.events,
batch_action_model=batch_actions[method],
collection_model=collection,
service_model=self._resource.meta.client.meta.service_model,
)
else:
document_collection_method(
section=method_section,
resource_name=self._resource_name,
action_name=method,
event_emitter=self._resource.meta.client.meta.events,
collection_model=collection,
service_model=self._resource.meta.client.meta.service_model,
)
def document_collection_object(
section,
collection_model,
include_signature=True,
):
"""Documents a collection resource object
:param section: The section to write to
:param collection_model: The model of the collection
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
if include_signature:
full_collection_name = (
f"{section.context.get('qualifier', '')}{collection_model.name}"
)
section.style.start_sphinx_py_attr(full_collection_name)
section.include_doc_string(
f'A collection of {collection_model.resource.type} resources.'
)
section.include_doc_string(
f'A {collection_model.resource.type} Collection will include all '
f'resources by default, and extreme caution should be taken when '
f'performing actions on all resources.'
)
def document_batch_action(
section,
resource_name,
event_emitter,
batch_action_model,
service_model,
collection_model,
include_signature=True,
):
"""Documents a collection's batch action
:param section: The section to write to
:param resource_name: The name of the resource
:param action_name: The name of collection action. Currently only
can be all, filter, limit, or page_size
:param event_emitter: The event emitter to use to emit events
:param batch_action_model: The model of the batch action
:param collection_model: The model of the collection
:param service_model: The model of the service
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
operation_model = service_model.operation_model(
batch_action_model.request.operation
)
ignore_params = get_resource_ignore_params(
batch_action_model.request.params
)
example_return_value = 'response'
if batch_action_model.resource:
example_return_value = xform_name(batch_action_model.resource.type)
example_resource_name = xform_name(resource_name)
if service_model.service_name == resource_name:
example_resource_name = resource_name
example_prefix = '{} = {}.{}.{}'.format(
example_return_value,
example_resource_name,
collection_model.name,
batch_action_model.name,
)
document_model_driven_resource_method(
section=section,
method_name=batch_action_model.name,
operation_model=operation_model,
event_emitter=event_emitter,
method_description=operation_model.documentation,
example_prefix=example_prefix,
exclude_input=ignore_params,
resource_action_model=batch_action_model,
include_signature=include_signature,
)
def document_collection_method(
section,
resource_name,
action_name,
event_emitter,
collection_model,
service_model,
include_signature=True,
):
"""Documents a collection method
:param section: The section to write to
:param resource_name: The name of the resource
:param action_name: The name of collection action. Currently only
can be all, filter, limit, or page_size
:param event_emitter: The event emitter to use to emit events
:param collection_model: The model of the collection
:param service_model: The model of the service
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
operation_model = service_model.operation_model(
collection_model.request.operation
)
underlying_operation_members = []
if operation_model.input_shape:
underlying_operation_members = operation_model.input_shape.members
example_resource_name = xform_name(resource_name)
if service_model.service_name == resource_name:
example_resource_name = resource_name
custom_action_info_dict = {
'all': {
'method_description': (
f'Creates an iterable of all {collection_model.resource.type} '
f'resources in the collection.'
),
'example_prefix': '{}_iterator = {}.{}.all'.format(
xform_name(collection_model.resource.type),
example_resource_name,
collection_model.name,
),
'exclude_input': underlying_operation_members,
},
'filter': {
'method_description': (
f'Creates an iterable of all {collection_model.resource.type} '
f'resources in the collection filtered by kwargs passed to '
f'method. A {collection_model.resource.type} collection will '
f'include all resources by default if no filters are provided, '
f'and extreme caution should be taken when performing actions '
f'on all resources.'
),
'example_prefix': '{}_iterator = {}.{}.filter'.format(
xform_name(collection_model.resource.type),
example_resource_name,
collection_model.name,
),
'exclude_input': get_resource_ignore_params(
collection_model.request.params
),
},
'limit': {
'method_description': (
f'Creates an iterable up to a specified amount of '
f'{collection_model.resource.type} resources in the collection.'
),
'example_prefix': '{}_iterator = {}.{}.limit'.format(
xform_name(collection_model.resource.type),
example_resource_name,
collection_model.name,
),
'include_input': [
DocumentedShape(
name='count',
type_name='integer',
documentation=(
'The limit to the number of resources '
'in the iterable.'
),
)
],
'exclude_input': underlying_operation_members,
},
'page_size': {
'method_description': (
f'Creates an iterable of all {collection_model.resource.type} '
f'resources in the collection, but limits the number of '
f'items returned by each service call by the specified amount.'
),
'example_prefix': '{}_iterator = {}.{}.page_size'.format(
xform_name(collection_model.resource.type),
example_resource_name,
collection_model.name,
),
'include_input': [
DocumentedShape(
name='count',
type_name='integer',
documentation=(
'The number of items returned by each ' 'service call'
),
)
],
'exclude_input': underlying_operation_members,
},
}
if action_name in custom_action_info_dict:
action_info = custom_action_info_dict[action_name]
document_model_driven_resource_method(
section=section,
method_name=action_name,
operation_model=operation_model,
event_emitter=event_emitter,
resource_action_model=collection_model,
include_signature=include_signature,
**action_info,
)

@ -0,0 +1,77 @@
# Copyright 2015 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.
from botocore.docs.docstring import LazyLoadedDocstring
from boto3.docs.action import document_action, document_load_reload_action
from boto3.docs.attr import (
document_attribute,
document_identifier,
document_reference,
)
from boto3.docs.collection import (
document_batch_action,
document_collection_method,
document_collection_object,
)
from boto3.docs.subresource import document_sub_resource
from boto3.docs.waiter import document_resource_waiter
class ActionDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_action(*args, **kwargs)
class LoadReloadDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_load_reload_action(*args, **kwargs)
class SubResourceDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_sub_resource(*args, **kwargs)
class AttributeDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_attribute(*args, **kwargs)
class IdentifierDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_identifier(*args, **kwargs)
class ReferenceDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_reference(*args, **kwargs)
class CollectionDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_collection_object(*args, **kwargs)
class CollectionMethodDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_collection_method(*args, **kwargs)
class BatchActionDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_batch_action(*args, **kwargs)
class ResourceWaiterDocstring(LazyLoadedDocstring):
def _write_docstring(self, *args, **kwargs):
document_resource_waiter(*args, **kwargs)

@ -0,0 +1,77 @@
# Copyright 2015 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.
from botocore.docs.method import document_model_driven_method
def document_model_driven_resource_method(
section,
method_name,
operation_model,
event_emitter,
method_description=None,
example_prefix=None,
include_input=None,
include_output=None,
exclude_input=None,
exclude_output=None,
document_output=True,
resource_action_model=None,
include_signature=True,
):
document_model_driven_method(
section=section,
method_name=method_name,
operation_model=operation_model,
event_emitter=event_emitter,
method_description=method_description,
example_prefix=example_prefix,
include_input=include_input,
include_output=include_output,
exclude_input=exclude_input,
exclude_output=exclude_output,
document_output=document_output,
include_signature=include_signature,
)
# If this action returns a resource modify the return example to
# appropriately reflect that.
if resource_action_model.resource:
if 'return' in section.available_sections:
section.delete_section('return')
resource_type = resource_action_model.resource.type
new_return_section = section.add_new_section('return')
return_resource_type = '{}.{}'.format(
operation_model.service_model.service_name, resource_type
)
return_type = f':py:class:`{return_resource_type}`'
return_description = f'{resource_type} resource'
if _method_returns_resource_list(resource_action_model.resource):
return_type = f'list({return_type})'
return_description = f'A list of {resource_type} resources'
new_return_section.style.new_line()
new_return_section.write(f':rtype: {return_type}')
new_return_section.style.new_line()
new_return_section.write(f':returns: {return_description}')
new_return_section.style.new_line()
def _method_returns_resource_list(resource):
for identifier in resource.identifiers:
if identifier.path and '[]' in identifier.path:
return True
return False

@ -0,0 +1,364 @@
# Copyright 2015 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 os
from botocore import xform_name
from botocore.docs.bcdoc.restdoc import DocumentStructure
from botocore.docs.utils import get_official_service_name
from boto3.docs.action import ActionDocumenter
from boto3.docs.attr import (
document_attribute,
document_identifier,
document_reference,
)
from boto3.docs.base import BaseDocumenter
from boto3.docs.collection import CollectionDocumenter
from boto3.docs.subresource import SubResourceDocumenter
from boto3.docs.utils import (
add_resource_type_overview,
get_identifier_args_for_signature,
get_identifier_description,
get_identifier_values_for_example,
)
from boto3.docs.waiter import WaiterResourceDocumenter
class ResourceDocumenter(BaseDocumenter):
def __init__(self, resource, botocore_session, root_docs_path):
super().__init__(resource)
self._botocore_session = botocore_session
self._root_docs_path = root_docs_path
self._resource_sub_path = self._resource_name.lower()
if self._resource_name == self._service_name:
self._resource_sub_path = 'service-resource'
def document_resource(self, section):
self._add_title(section)
self._add_resource_note(section)
self._add_intro(section)
self._add_identifiers(section)
self._add_attributes(section)
self._add_references(section)
self._add_actions(section)
self._add_sub_resources(section)
self._add_collections(section)
self._add_waiters(section)
def _add_title(self, section):
title_section = section.add_new_section('title')
title_section.style.h2(self._resource_name)
def _add_intro(self, section):
identifier_names = []
if self._resource_model.identifiers:
for identifier in self._resource_model.identifiers:
identifier_names.append(identifier.name)
# Write out the class signature.
class_args = get_identifier_args_for_signature(identifier_names)
start_class = section.add_new_section('start_class')
start_class.style.start_sphinx_py_class(
class_name=f'{self.class_name}({class_args})'
)
# Add as short description about the resource
description_section = start_class.add_new_section('description')
self._add_description(description_section)
# Add an example of how to instantiate the resource
example_section = start_class.add_new_section('example')
self._add_example(example_section, identifier_names)
# Add the description for the parameters to instantiate the
# resource.
param_section = start_class.add_new_section('params')
self._add_params_description(param_section, identifier_names)
end_class = section.add_new_section('end_class')
end_class.style.end_sphinx_py_class()
def _add_description(self, section):
official_service_name = get_official_service_name(self._service_model)
section.write(
'A resource representing an {} {}'.format(
official_service_name, self._resource_name
)
)
def _add_example(self, section, identifier_names):
section.style.start_codeblock()
section.style.new_line()
section.write('import boto3')
section.style.new_line()
section.style.new_line()
section.write(
'{} = boto3.resource(\'{}\')'.format(
self._service_name, self._service_name
)
)
section.style.new_line()
example_values = get_identifier_values_for_example(identifier_names)
section.write(
'{} = {}.{}({})'.format(
xform_name(self._resource_name),
self._service_name,
self._resource_name,
example_values,
)
)
section.style.end_codeblock()
def _add_params_description(self, section, identifier_names):
for identifier_name in identifier_names:
description = get_identifier_description(
self._resource_name, identifier_name
)
section.write(f':type {identifier_name}: string')
section.style.new_line()
section.write(f':param {identifier_name}: {description}')
section.style.new_line()
def _add_overview_of_member_type(self, section, resource_member_type):
section.style.new_line()
section.write(
f'These are the resource\'s available {resource_member_type}:'
)
section.style.new_line()
section.style.toctree()
for member in self.member_map[resource_member_type]:
section.style.tocitem(f'{member}')
def _add_identifiers(self, section):
identifiers = self._resource.meta.resource_model.identifiers
section = section.add_new_section('identifiers')
member_list = []
if identifiers:
self.member_map['identifiers'] = member_list
add_resource_type_overview(
section=section,
resource_type='Identifiers',
description=(
'Identifiers are properties of a resource that are '
'set upon instantiation of the resource.'
),
intro_link='identifiers_attributes_intro',
)
for identifier in identifiers:
member_list.append(identifier.name)
# Create a new DocumentStructure for each identifier and add contents.
identifier_doc = DocumentStructure(identifier.name, target='html')
breadcrumb_section = identifier_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Identifier / {identifier.name}')
identifier_doc.add_title_section(identifier.name)
identifier_section = identifier_doc.add_new_section(
identifier.name,
context={'qualifier': f'{self.class_name}.'},
)
document_identifier(
section=identifier_section,
resource_name=self._resource_name,
identifier_model=identifier,
)
# Write identifiers in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<identifier_name>.rst
identifiers_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
identifier_doc.write_to_file(identifiers_dir_path, identifier.name)
if identifiers:
self._add_overview_of_member_type(section, 'identifiers')
def _add_attributes(self, section):
service_model = self._resource.meta.client.meta.service_model
attributes = {}
if self._resource.meta.resource_model.shape:
shape = service_model.shape_for(
self._resource.meta.resource_model.shape
)
attributes = self._resource.meta.resource_model.get_attributes(
shape
)
section = section.add_new_section('attributes')
attribute_list = []
if attributes:
add_resource_type_overview(
section=section,
resource_type='Attributes',
description=(
'Attributes provide access'
' to the properties of a resource. Attributes are lazy-'
'loaded the first time one is accessed via the'
' :py:meth:`load` method.'
),
intro_link='identifiers_attributes_intro',
)
self.member_map['attributes'] = attribute_list
for attr_name in sorted(attributes):
_, attr_shape = attributes[attr_name]
attribute_list.append(attr_name)
# Create a new DocumentStructure for each attribute and add contents.
attribute_doc = DocumentStructure(attr_name, target='html')
breadcrumb_section = attribute_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Attribute / {attr_name}')
attribute_doc.add_title_section(attr_name)
attribute_section = attribute_doc.add_new_section(
attr_name,
context={'qualifier': f'{self.class_name}.'},
)
document_attribute(
section=attribute_section,
service_name=self._service_name,
resource_name=self._resource_name,
attr_name=attr_name,
event_emitter=self._resource.meta.client.meta.events,
attr_model=attr_shape,
)
# Write attributes in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<attribute_name>.rst
attributes_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
attribute_doc.write_to_file(attributes_dir_path, attr_name)
if attributes:
self._add_overview_of_member_type(section, 'attributes')
def _add_references(self, section):
section = section.add_new_section('references')
references = self._resource.meta.resource_model.references
reference_list = []
if references:
add_resource_type_overview(
section=section,
resource_type='References',
description=(
'References are related resource instances that have '
'a belongs-to relationship.'
),
intro_link='references_intro',
)
self.member_map['references'] = reference_list
self._add_overview_of_member_type(section, 'references')
for reference in references:
reference_list.append(reference.name)
# Create a new DocumentStructure for each reference and add contents.
reference_doc = DocumentStructure(reference.name, target='html')
breadcrumb_section = reference_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Reference / {reference.name}')
reference_doc.add_title_section(reference.name)
reference_section = reference_doc.add_new_section(
reference.name,
context={'qualifier': f'{self.class_name}.'},
)
document_reference(
section=reference_section,
reference_model=reference,
)
# Write references in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<reference_name>.rst
references_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
reference_doc.write_to_file(references_dir_path, reference.name)
if references:
self._add_overview_of_member_type(section, 'references')
def _add_actions(self, section):
section = section.add_new_section('actions')
actions = self._resource.meta.resource_model.actions
if actions:
documenter = ActionDocumenter(self._resource, self._root_docs_path)
documenter.member_map = self.member_map
documenter.document_actions(section)
self._add_overview_of_member_type(section, 'actions')
def _add_sub_resources(self, section):
section = section.add_new_section('sub-resources')
sub_resources = self._resource.meta.resource_model.subresources
if sub_resources:
documenter = SubResourceDocumenter(
self._resource, self._root_docs_path
)
documenter.member_map = self.member_map
documenter.document_sub_resources(section)
self._add_overview_of_member_type(section, 'sub-resources')
def _add_collections(self, section):
section = section.add_new_section('collections')
collections = self._resource.meta.resource_model.collections
if collections:
documenter = CollectionDocumenter(
self._resource, self._root_docs_path
)
documenter.member_map = self.member_map
documenter.document_collections(section)
self._add_overview_of_member_type(section, 'collections')
def _add_waiters(self, section):
section = section.add_new_section('waiters')
waiters = self._resource.meta.resource_model.waiters
if waiters:
service_waiter_model = self._botocore_session.get_waiter_model(
self._service_name
)
documenter = WaiterResourceDocumenter(
self._resource, service_waiter_model, self._root_docs_path
)
documenter.member_map = self.member_map
documenter.document_resource_waiters(section)
self._add_overview_of_member_type(section, 'waiters')
def _add_resource_note(self, section):
section = section.add_new_section('feature-freeze')
section.style.start_note()
section.write(
"Before using anything on this page, please refer to the resources "
":doc:`user guide <../../../../guide/resources>` for the most recent "
"guidance on using resources."
)
section.style.end_note()
class ServiceResourceDocumenter(ResourceDocumenter):
@property
def class_name(self):
return f'{self._service_docs_name}.ServiceResource'
def _add_title(self, section):
title_section = section.add_new_section('title')
title_section.style.h2('Service Resource')
def _add_description(self, section):
official_service_name = get_official_service_name(self._service_model)
section.write(f'A resource representing {official_service_name}')
def _add_example(self, section, identifier_names):
section.style.start_codeblock()
section.style.new_line()
section.write('import boto3')
section.style.new_line()
section.style.new_line()
section.write(
f'{self._service_name} = boto3.resource(\'{self._service_name}\')'
)
section.style.end_codeblock()

@ -0,0 +1,202 @@
# Copyright 2015 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 os
from botocore.docs.bcdoc.restdoc import DocumentStructure
from botocore.docs.service import ServiceDocumenter as BaseServiceDocumenter
from botocore.exceptions import DataNotFoundError
import boto3
from boto3.docs.client import Boto3ClientDocumenter
from boto3.docs.resource import ResourceDocumenter, ServiceResourceDocumenter
from boto3.utils import ServiceContext
class ServiceDocumenter(BaseServiceDocumenter):
# The path used to find examples
EXAMPLE_PATH = os.path.join(os.path.dirname(boto3.__file__), 'examples')
def __init__(self, service_name, session, root_docs_path):
super().__init__(
service_name=service_name,
# I know that this is an internal attribute, but the botocore session
# is needed to load the paginator and waiter models.
session=session._session,
root_docs_path=root_docs_path,
)
self._boto3_session = session
self._client = self._boto3_session.client(service_name)
self._service_resource = None
if self._service_name in self._boto3_session.get_available_resources():
self._service_resource = self._boto3_session.resource(service_name)
self.sections = [
'title',
'client',
'paginators',
'waiters',
'resources',
'examples',
'context-params',
]
self._root_docs_path = root_docs_path
self._USER_GUIDE_LINK = (
'https://boto3.amazonaws.com/'
'v1/documentation/api/latest/guide/resources.html'
)
def document_service(self):
"""Documents an entire service.
:returns: The reStructured text of the documented service.
"""
doc_structure = DocumentStructure(
self._service_name, section_names=self.sections, target='html'
)
self.title(doc_structure.get_section('title'))
self.client_api(doc_structure.get_section('client'))
self.paginator_api(doc_structure.get_section('paginators'))
self.waiter_api(doc_structure.get_section('waiters'))
if self._service_resource:
self.resource_section(doc_structure.get_section('resources'))
self._document_examples(doc_structure.get_section('examples'))
context_params_section = doc_structure.get_section('context-params')
self.client_context_params(context_params_section)
return doc_structure.flush_structure()
def client_api(self, section):
examples = None
try:
examples = self.get_examples(self._service_name)
except DataNotFoundError:
pass
Boto3ClientDocumenter(
self._client, self._root_docs_path, examples
).document_client(section)
def resource_section(self, section):
section.style.h2('Resources')
section.style.new_line()
section.write(
'Resources are available in boto3 via the '
'``resource`` method. For more detailed instructions '
'and examples on the usage of resources, see the '
'resources '
)
section.style.external_link(
title='user guide',
link=self._USER_GUIDE_LINK,
)
section.write('.')
section.style.new_line()
section.style.new_line()
section.write('The available resources are:')
section.style.new_line()
section.style.toctree()
self._document_service_resource(section)
self._document_resources(section)
def _document_service_resource(self, section):
# Create a new DocumentStructure for each Service Resource and add contents.
service_resource_doc = DocumentStructure(
'service-resource', target='html'
)
breadcrumb_section = service_resource_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(
self._client.__class__.__name__, f'../../{self._service_name}'
)
breadcrumb_section.write(' / Resource / ServiceResource')
ServiceResourceDocumenter(
self._service_resource, self._session, self._root_docs_path
).document_resource(service_resource_doc)
# Write collections in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<collection_name>.rst
resource_name = self._service_resource.meta.resource_model.name
if resource_name == self._service_name:
resource_name = 'service-resource'
service_resource_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{resource_name.lower()}',
)
service_resource_doc.write_to_file(service_resource_dir_path, 'index')
section.style.tocitem(f'{self._service_name}/{resource_name}/index')
def _document_resources(self, section):
temp_identifier_value = 'foo'
loader = self._session.get_component('data_loader')
json_resource_model = loader.load_service_model(
self._service_name, 'resources-1'
)
service_model = self._service_resource.meta.client.meta.service_model
for resource_name in json_resource_model['resources']:
resource_model = json_resource_model['resources'][resource_name]
resource_cls = (
self._boto3_session.resource_factory.load_from_definition(
resource_name=resource_name,
single_resource_json_definition=resource_model,
service_context=ServiceContext(
service_name=self._service_name,
resource_json_definitions=json_resource_model[
'resources'
],
service_model=service_model,
service_waiter_model=None,
),
)
)
identifiers = resource_cls.meta.resource_model.identifiers
args = []
for _ in identifiers:
args.append(temp_identifier_value)
resource = resource_cls(*args, client=self._client)
# Create a new DocumentStructure for each Resource and add contents.
resource_name = resource.meta.resource_model.name.lower()
resource_doc = DocumentStructure(resource_name, target='html')
breadcrumb_section = resource_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(
self._client.__class__.__name__, f'../../{self._service_name}'
)
breadcrumb_section.write(
f' / Resource / {resource.meta.resource_model.name}'
)
ResourceDocumenter(
resource, self._session, self._root_docs_path
).document_resource(
resource_doc.add_new_section(resource.meta.resource_model.name)
)
# Write collections in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<index>.rst
service_resource_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{resource_name}',
)
resource_doc.write_to_file(service_resource_dir_path, 'index')
section.style.tocitem(
f'{self._service_name}/{resource_name}/index'
)
def _get_example_file(self):
return os.path.realpath(
os.path.join(self.EXAMPLE_PATH, self._service_name + '.rst')
)
def _document_examples(self, section):
examples_file = self._get_example_file()
if os.path.isfile(examples_file):
section.style.h2('Examples')
section.style.new_line()
with open(examples_file) as f:
section.write(f.read())

@ -0,0 +1,153 @@
# Copyright 2015 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 os
from botocore import xform_name
from botocore.docs.bcdoc.restdoc import DocumentStructure
from botocore.utils import get_service_module_name
from boto3.docs.base import NestedDocumenter
from boto3.docs.utils import (
add_resource_type_overview,
get_identifier_args_for_signature,
get_identifier_description,
get_identifier_values_for_example,
)
class SubResourceDocumenter(NestedDocumenter):
def document_sub_resources(self, section):
add_resource_type_overview(
section=section,
resource_type='Sub-resources',
description=(
'Sub-resources are methods that create a new instance of a'
' child resource. This resource\'s identifiers get passed'
' along to the child.'
),
intro_link='subresources_intro',
)
sub_resources = sorted(
self._resource.meta.resource_model.subresources,
key=lambda sub_resource: sub_resource.name,
)
sub_resources_list = []
self.member_map['sub-resources'] = sub_resources_list
for sub_resource in sub_resources:
sub_resources_list.append(sub_resource.name)
# Create a new DocumentStructure for each sub_resource and add contents.
sub_resource_doc = DocumentStructure(
sub_resource.name, target='html'
)
breadcrumb_section = sub_resource_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Sub-Resource / {sub_resource.name}')
sub_resource_doc.add_title_section(sub_resource.name)
sub_resource_section = sub_resource_doc.add_new_section(
sub_resource.name,
context={'qualifier': f'{self.class_name}.'},
)
document_sub_resource(
section=sub_resource_section,
resource_name=self._resource_name,
sub_resource_model=sub_resource,
service_model=self._service_model,
)
# Write sub_resources in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<sub_resource_name>.rst
sub_resources_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
sub_resource_doc.write_to_file(
sub_resources_dir_path, sub_resource.name
)
def document_sub_resource(
section,
resource_name,
sub_resource_model,
service_model,
include_signature=True,
):
"""Documents a resource action
:param section: The section to write to
:param resource_name: The name of the resource
:param sub_resource_model: The model of the subresource
:param service_model: The model of the service
:param include_signature: Whether or not to include the signature.
It is useful for generating docstrings.
"""
identifiers_needed = []
for identifier in sub_resource_model.resource.identifiers:
if identifier.source == 'input':
identifiers_needed.append(xform_name(identifier.target))
if include_signature:
signature_args = get_identifier_args_for_signature(identifiers_needed)
full_sub_resource_name = (
f"{section.context.get('qualifier', '')}{sub_resource_model.name}"
)
section.style.start_sphinx_py_method(
full_sub_resource_name, signature_args
)
method_intro_section = section.add_new_section('method-intro')
description = f'Creates a {sub_resource_model.resource.type} resource.'
method_intro_section.include_doc_string(description)
example_section = section.add_new_section('example')
example_values = get_identifier_values_for_example(identifiers_needed)
example_resource_name = xform_name(resource_name)
if service_model.service_name == resource_name:
example_resource_name = resource_name
example = '{} = {}.{}({})'.format(
xform_name(sub_resource_model.resource.type),
example_resource_name,
sub_resource_model.name,
example_values,
)
example_section.style.start_codeblock()
example_section.write(example)
example_section.style.end_codeblock()
param_section = section.add_new_section('params')
for identifier in identifiers_needed:
description = get_identifier_description(
sub_resource_model.name, identifier
)
param_section.write(f':type {identifier}: string')
param_section.style.new_line()
param_section.write(f':param {identifier}: {description}')
param_section.style.new_line()
return_section = section.add_new_section('return')
return_section.style.new_line()
return_section.write(
':rtype: :py:class:`{}.{}`'.format(
get_service_module_name(service_model),
sub_resource_model.resource.type,
)
)
return_section.style.new_line()
return_section.write(
f':returns: A {sub_resource_model.resource.type} resource'
)
return_section.style.new_line()

@ -0,0 +1,146 @@
# Copyright 2015 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 inspect
import jmespath
def get_resource_ignore_params(params):
"""Helper method to determine which parameters to ignore for actions
:returns: A list of the parameter names that does not need to be
included in a resource's method call for documentation purposes.
"""
ignore_params = []
for param in params:
result = jmespath.compile(param.target)
current = result.parsed
# Use JMESPath to find the left most element in the target expression
# which will be the parameter to ignore in the action call.
while current['children']:
current = current['children'][0]
# Make sure the parameter we are about to ignore is a field.
# If it is not, we should ignore the result to avoid false positives.
if current['type'] == 'field':
ignore_params.append(current['value'])
return ignore_params
def is_resource_action(action_handle):
return inspect.isfunction(action_handle)
def get_resource_public_actions(resource_class):
resource_class_members = inspect.getmembers(resource_class)
resource_methods = {}
for name, member in resource_class_members:
if not name.startswith('_'):
if not name[0].isupper():
if not name.startswith('wait_until'):
if is_resource_action(member):
resource_methods[name] = member
return resource_methods
def get_identifier_values_for_example(identifier_names):
return ','.join([f'\'{identifier}\'' for identifier in identifier_names])
def get_identifier_args_for_signature(identifier_names):
return ','.join(identifier_names)
def get_identifier_description(resource_name, identifier_name):
return (
f"The {resource_name}'s {identifier_name} identifier. "
f"This **must** be set."
)
def add_resource_type_overview(
section, resource_type, description, intro_link=None
):
section.style.new_line()
section.style.h3(resource_type)
section.style.new_line()
section.style.new_line()
section.write(description)
section.style.new_line()
if intro_link is not None:
section.write(
f'For more information about {resource_type.lower()} refer to the '
f':ref:`Resources Introduction Guide<{intro_link}>`.'
)
section.style.new_line()
class DocumentModifiedShape:
def __init__(
self, shape_name, new_type, new_description, new_example_value
):
self._shape_name = shape_name
self._new_type = new_type
self._new_description = new_description
self._new_example_value = new_example_value
def replace_documentation_for_matching_shape(
self, event_name, section, **kwargs
):
if self._shape_name == section.context.get('shape'):
self._replace_documentation(event_name, section)
for section_name in section.available_sections:
sub_section = section.get_section(section_name)
if self._shape_name == sub_section.context.get('shape'):
self._replace_documentation(event_name, sub_section)
else:
self.replace_documentation_for_matching_shape(
event_name, sub_section
)
def _replace_documentation(self, event_name, section):
if event_name.startswith(
'docs.request-example'
) or event_name.startswith('docs.response-example'):
section.remove_all_sections()
section.clear_text()
section.write(self._new_example_value)
if event_name.startswith(
'docs.request-params'
) or event_name.startswith('docs.response-params'):
allowed_sections = (
'param-name',
'param-documentation',
'end-structure',
'param-type',
'end-param',
)
for section_name in section.available_sections:
# Delete any extra members as a new shape is being
# used.
if section_name not in allowed_sections:
section.delete_section(section_name)
# Update the documentation
description_section = section.get_section('param-documentation')
description_section.clear_text()
description_section.write(self._new_description)
# Update the param type
type_section = section.get_section('param-type')
if type_section.getvalue().decode('utf-8').startswith(':type'):
type_section.clear_text()
type_section.write(f':type {section.name}: {self._new_type}')
else:
type_section.clear_text()
type_section.style.italics(f'({self._new_type}) -- ')

@ -0,0 +1,130 @@
# Copyright 2015 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 os
from botocore import xform_name
from botocore.docs.bcdoc.restdoc import DocumentStructure
from botocore.docs.method import document_model_driven_method
from botocore.utils import get_service_module_name
from boto3.docs.base import NestedDocumenter
from boto3.docs.utils import (
add_resource_type_overview,
get_resource_ignore_params,
)
class WaiterResourceDocumenter(NestedDocumenter):
def __init__(self, resource, service_waiter_model, root_docs_path):
super().__init__(resource, root_docs_path)
self._service_waiter_model = service_waiter_model
def document_resource_waiters(self, section):
waiters = self._resource.meta.resource_model.waiters
add_resource_type_overview(
section=section,
resource_type='Waiters',
description=(
'Waiters provide an interface to wait for a resource'
' to reach a specific state.'
),
intro_link='waiters_intro',
)
waiter_list = []
self.member_map['waiters'] = waiter_list
for waiter in waiters:
waiter_list.append(waiter.name)
# Create a new DocumentStructure for each waiter and add contents.
waiter_doc = DocumentStructure(waiter.name, target='html')
breadcrumb_section = waiter_doc.add_new_section('breadcrumb')
breadcrumb_section.style.ref(self._resource_class_name, 'index')
breadcrumb_section.write(f' / Waiter / {waiter.name}')
waiter_doc.add_title_section(waiter.name)
waiter_section = waiter_doc.add_new_section(
waiter.name,
context={'qualifier': f'{self.class_name}.'},
)
document_resource_waiter(
section=waiter_section,
resource_name=self._resource_name,
event_emitter=self._resource.meta.client.meta.events,
service_model=self._service_model,
resource_waiter_model=waiter,
service_waiter_model=self._service_waiter_model,
)
# Write waiters in individual/nested files.
# Path: <root>/reference/services/<service>/<resource_name>/<waiter_name>.rst
waiters_dir_path = os.path.join(
self._root_docs_path,
f'{self._service_name}',
f'{self._resource_sub_path}',
)
waiter_doc.write_to_file(waiters_dir_path, waiter.name)
def document_resource_waiter(
section,
resource_name,
event_emitter,
service_model,
resource_waiter_model,
service_waiter_model,
include_signature=True,
):
waiter_model = service_waiter_model.get_waiter(
resource_waiter_model.waiter_name
)
operation_model = service_model.operation_model(waiter_model.operation)
ignore_params = get_resource_ignore_params(resource_waiter_model.params)
service_module_name = get_service_module_name(service_model)
description = (
'Waits until this {} is {}. This method calls '
':py:meth:`{}.Waiter.{}.wait` which polls '
':py:meth:`{}.Client.{}` every {} seconds until '
'a successful state is reached. An error is returned '
'after {} failed checks.'.format(
resource_name,
' '.join(resource_waiter_model.name.split('_')[2:]),
service_module_name,
xform_name(resource_waiter_model.waiter_name),
service_module_name,
xform_name(waiter_model.operation),
waiter_model.delay,
waiter_model.max_attempts,
)
)
example_prefix = '{}.{}'.format(
xform_name(resource_name), resource_waiter_model.name
)
full_waiter_name = (
f"{section.context.get('qualifier', '')}{resource_waiter_model.name}"
)
document_model_driven_method(
section=section,
method_name=full_waiter_name,
operation_model=operation_model,
event_emitter=event_emitter,
example_prefix=example_prefix,
method_description=description,
exclude_input=ignore_params,
include_signature=include_signature,
)
if 'return' in section.available_sections:
# Waiters do not return anything so we should remove
# any sections that may document the underlying return
# value of the client method.
return_section = section.get_section('return')
return_section.clear_text()
return_section.remove_all_sections()
return_section.write(':returns: None')

@ -0,0 +1,12 @@
# Copyright 2015 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.

@ -0,0 +1,461 @@
# Copyright 2015 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
from collections import namedtuple
from boto3.exceptions import (
DynamoDBNeedsConditionError,
DynamoDBNeedsKeyConditionError,
DynamoDBOperationNotSupportedError,
)
ATTR_NAME_REGEX = re.compile(r'[^.\[\]]+(?![^\[]*\])')
class ConditionBase:
expression_format = ''
expression_operator = ''
has_grouped_values = False
def __init__(self, *values):
self._values = values
def __and__(self, other):
if not isinstance(other, ConditionBase):
raise DynamoDBOperationNotSupportedError('AND', other)
return And(self, other)
def __or__(self, other):
if not isinstance(other, ConditionBase):
raise DynamoDBOperationNotSupportedError('OR', other)
return Or(self, other)
def __invert__(self):
return Not(self)
def get_expression(self):
return {
'format': self.expression_format,
'operator': self.expression_operator,
'values': self._values,
}
def __eq__(self, other):
if isinstance(other, type(self)):
if self._values == other._values:
return True
return False
def __ne__(self, other):
return not self.__eq__(other)
class AttributeBase:
def __init__(self, name):
self.name = name
def __and__(self, value):
raise DynamoDBOperationNotSupportedError('AND', self)
def __or__(self, value):
raise DynamoDBOperationNotSupportedError('OR', self)
def __invert__(self):
raise DynamoDBOperationNotSupportedError('NOT', self)
def eq(self, value):
"""Creates a condition where the attribute is equal to the value.
:param value: The value that the attribute is equal to.
"""
return Equals(self, value)
def lt(self, value):
"""Creates a condition where the attribute is less than the value.
:param value: The value that the attribute is less than.
"""
return LessThan(self, value)
def lte(self, value):
"""Creates a condition where the attribute is less than or equal to the
value.
:param value: The value that the attribute is less than or equal to.
"""
return LessThanEquals(self, value)
def gt(self, value):
"""Creates a condition where the attribute is greater than the value.
:param value: The value that the attribute is greater than.
"""
return GreaterThan(self, value)
def gte(self, value):
"""Creates a condition where the attribute is greater than or equal to
the value.
:param value: The value that the attribute is greater than or equal to.
"""
return GreaterThanEquals(self, value)
def begins_with(self, value):
"""Creates a condition where the attribute begins with the value.
:param value: The value that the attribute begins with.
"""
return BeginsWith(self, value)
def between(self, low_value, high_value):
"""Creates a condition where the attribute is greater than or equal
to the low value and less than or equal to the high value.
:param low_value: The value that the attribute is greater than or equal to.
:param high_value: The value that the attribute is less than or equal to.
"""
return Between(self, low_value, high_value)
def __eq__(self, other):
return isinstance(other, type(self)) and self.name == other.name
def __ne__(self, other):
return not self.__eq__(other)
class ConditionAttributeBase(ConditionBase, AttributeBase):
"""This base class is for conditions that can have attribute methods.
One example is the Size condition. To complete a condition, you need
to apply another AttributeBase method like eq().
"""
def __init__(self, *values):
ConditionBase.__init__(self, *values)
# This is assuming the first value to the condition is the attribute
# in which can be used to generate its attribute base.
AttributeBase.__init__(self, values[0].name)
def __eq__(self, other):
return ConditionBase.__eq__(self, other) and AttributeBase.__eq__(
self, other
)
def __ne__(self, other):
return not self.__eq__(other)
class ComparisonCondition(ConditionBase):
expression_format = '{0} {operator} {1}'
class Equals(ComparisonCondition):
expression_operator = '='
class NotEquals(ComparisonCondition):
expression_operator = '<>'
class LessThan(ComparisonCondition):
expression_operator = '<'
class LessThanEquals(ComparisonCondition):
expression_operator = '<='
class GreaterThan(ComparisonCondition):
expression_operator = '>'
class GreaterThanEquals(ComparisonCondition):
expression_operator = '>='
class In(ComparisonCondition):
expression_operator = 'IN'
has_grouped_values = True
class Between(ConditionBase):
expression_operator = 'BETWEEN'
expression_format = '{0} {operator} {1} AND {2}'
class BeginsWith(ConditionBase):
expression_operator = 'begins_with'
expression_format = '{operator}({0}, {1})'
class Contains(ConditionBase):
expression_operator = 'contains'
expression_format = '{operator}({0}, {1})'
class Size(ConditionAttributeBase):
expression_operator = 'size'
expression_format = '{operator}({0})'
class AttributeType(ConditionBase):
expression_operator = 'attribute_type'
expression_format = '{operator}({0}, {1})'
class AttributeExists(ConditionBase):
expression_operator = 'attribute_exists'
expression_format = '{operator}({0})'
class AttributeNotExists(ConditionBase):
expression_operator = 'attribute_not_exists'
expression_format = '{operator}({0})'
class And(ConditionBase):
expression_operator = 'AND'
expression_format = '({0} {operator} {1})'
class Or(ConditionBase):
expression_operator = 'OR'
expression_format = '({0} {operator} {1})'
class Not(ConditionBase):
expression_operator = 'NOT'
expression_format = '({operator} {0})'
class Key(AttributeBase):
pass
class Attr(AttributeBase):
"""Represents an DynamoDB item's attribute."""
def ne(self, value):
"""Creates a condition where the attribute is not equal to the value
:param value: The value that the attribute is not equal to.
"""
return NotEquals(self, value)
def is_in(self, value):
"""Creates a condition where the attribute is in the value,
:type value: list
:param value: The value that the attribute is in.
"""
return In(self, value)
def exists(self):
"""Creates a condition where the attribute exists."""
return AttributeExists(self)
def not_exists(self):
"""Creates a condition where the attribute does not exist."""
return AttributeNotExists(self)
def contains(self, value):
"""Creates a condition where the attribute contains the value.
:param value: The value the attribute contains.
"""
return Contains(self, value)
def size(self):
"""Creates a condition for the attribute size.
Note another AttributeBase method must be called on the returned
size condition to be a valid DynamoDB condition.
"""
return Size(self)
def attribute_type(self, value):
"""Creates a condition for the attribute type.
:param value: The type of the attribute.
"""
return AttributeType(self, value)
BuiltConditionExpression = namedtuple(
'BuiltConditionExpression',
[
'condition_expression',
'attribute_name_placeholders',
'attribute_value_placeholders',
],
)
class ConditionExpressionBuilder:
"""This class is used to build condition expressions with placeholders"""
def __init__(self):
self._name_count = 0
self._value_count = 0
self._name_placeholder = 'n'
self._value_placeholder = 'v'
def _get_name_placeholder(self):
return '#' + self._name_placeholder + str(self._name_count)
def _get_value_placeholder(self):
return ':' + self._value_placeholder + str(self._value_count)
def reset(self):
"""Resets the placeholder name and values"""
self._name_count = 0
self._value_count = 0
def build_expression(self, condition, is_key_condition=False):
"""Builds the condition expression and the dictionary of placeholders.
:type condition: ConditionBase
:param condition: A condition to be built into a condition expression
string with any necessary placeholders.
:type is_key_condition: Boolean
:param is_key_condition: True if the expression is for a
KeyConditionExpression. False otherwise.
:rtype: (string, dict, dict)
:returns: Will return a string representing the condition with
placeholders inserted where necessary, a dictionary of
placeholders for attribute names, and a dictionary of
placeholders for attribute values. Here is a sample return value:
('#n0 = :v0', {'#n0': 'myattribute'}, {':v1': 'myvalue'})
"""
if not isinstance(condition, ConditionBase):
raise DynamoDBNeedsConditionError(condition)
attribute_name_placeholders = {}
attribute_value_placeholders = {}
condition_expression = self._build_expression(
condition,
attribute_name_placeholders,
attribute_value_placeholders,
is_key_condition=is_key_condition,
)
return BuiltConditionExpression(
condition_expression=condition_expression,
attribute_name_placeholders=attribute_name_placeholders,
attribute_value_placeholders=attribute_value_placeholders,
)
def _build_expression(
self,
condition,
attribute_name_placeholders,
attribute_value_placeholders,
is_key_condition,
):
expression_dict = condition.get_expression()
replaced_values = []
for value in expression_dict['values']:
# Build the necessary placeholders for that value.
# Placeholders are built for both attribute names and values.
replaced_value = self._build_expression_component(
value,
attribute_name_placeholders,
attribute_value_placeholders,
condition.has_grouped_values,
is_key_condition,
)
replaced_values.append(replaced_value)
# Fill out the expression using the operator and the
# values that have been replaced with placeholders.
return expression_dict['format'].format(
*replaced_values, operator=expression_dict['operator']
)
def _build_expression_component(
self,
value,
attribute_name_placeholders,
attribute_value_placeholders,
has_grouped_values,
is_key_condition,
):
# Continue to recurse if the value is a ConditionBase in order
# to extract out all parts of the expression.
if isinstance(value, ConditionBase):
return self._build_expression(
value,
attribute_name_placeholders,
attribute_value_placeholders,
is_key_condition,
)
# If it is not a ConditionBase, we can recurse no further.
# So we check if it is an attribute and add placeholders for
# its name
elif isinstance(value, AttributeBase):
if is_key_condition and not isinstance(value, Key):
raise DynamoDBNeedsKeyConditionError(
f'Attribute object {value.name} is of type {type(value)}. '
f'KeyConditionExpression only supports Attribute objects '
f'of type Key'
)
return self._build_name_placeholder(
value, attribute_name_placeholders
)
# If it is anything else, we treat it as a value and thus placeholders
# are needed for the value.
else:
return self._build_value_placeholder(
value, attribute_value_placeholders, has_grouped_values
)
def _build_name_placeholder(self, value, attribute_name_placeholders):
attribute_name = value.name
# Figure out which parts of the attribute name that needs replacement.
attribute_name_parts = ATTR_NAME_REGEX.findall(attribute_name)
# Add a temporary placeholder for each of these parts.
placeholder_format = ATTR_NAME_REGEX.sub('%s', attribute_name)
str_format_args = []
for part in attribute_name_parts:
name_placeholder = self._get_name_placeholder()
self._name_count += 1
str_format_args.append(name_placeholder)
# Add the placeholder and value to dictionary of name placeholders.
attribute_name_placeholders[name_placeholder] = part
# Replace the temporary placeholders with the designated placeholders.
return placeholder_format % tuple(str_format_args)
def _build_value_placeholder(
self, value, attribute_value_placeholders, has_grouped_values=False
):
# If the values are grouped, we need to add a placeholder for
# each element inside of the actual value.
if has_grouped_values:
placeholder_list = []
for v in value:
value_placeholder = self._get_value_placeholder()
self._value_count += 1
placeholder_list.append(value_placeholder)
attribute_value_placeholders[value_placeholder] = v
# Assuming the values are grouped by parenthesis.
# IN is the currently the only one that uses this so it maybe
# needed to be changed in future.
return '(' + ', '.join(placeholder_list) + ')'
# Otherwise, treat the value as a single value that needs only
# one placeholder.
else:
value_placeholder = self._get_value_placeholder()
self._value_count += 1
attribute_value_placeholders[value_placeholder] = value
return value_placeholder

@ -0,0 +1,167 @@
# Copyright 2015 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
logger = logging.getLogger(__name__)
def register_table_methods(base_classes, **kwargs):
base_classes.insert(0, TableResource)
# This class can be used to add any additional methods we want
# onto a table resource. Ideally to avoid creating a new
# base class for every method we can just update this
# class instead. Just be sure to move the bulk of the
# actual method implementation to another class.
class TableResource:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def batch_writer(self, overwrite_by_pkeys=None):
"""Create a batch writer object.
This method creates a context manager for writing
objects to Amazon DynamoDB in batch.
The batch writer will automatically handle buffering and sending items
in batches. In addition, the batch writer will also automatically
handle any unprocessed items and resend them as needed. All you need
to do is call ``put_item`` for any items you want to add, and
``delete_item`` for any items you want to delete.
Example usage::
with table.batch_writer() as batch:
for _ in range(1000000):
batch.put_item(Item={'HashKey': '...',
'Otherstuff': '...'})
# You can also delete_items in a batch.
batch.delete_item(Key={'HashKey': 'SomeHashKey'})
:type overwrite_by_pkeys: list(string)
:param overwrite_by_pkeys: De-duplicate request items in buffer
if match new request item on specified primary keys. i.e
``["partition_key1", "sort_key2", "sort_key3"]``
"""
return BatchWriter(
self.name, self.meta.client, overwrite_by_pkeys=overwrite_by_pkeys
)
class BatchWriter:
"""Automatically handle batch writes to DynamoDB for a single table."""
def __init__(
self, table_name, client, flush_amount=25, overwrite_by_pkeys=None
):
"""
:type table_name: str
:param table_name: The name of the table. The class handles
batch writes to a single table.
:type client: ``botocore.client.Client``
:param client: A botocore client. Note this client
**must** have the dynamodb customizations applied
to it for transforming AttributeValues into the
wire protocol. What this means in practice is that
you need to use a client that comes from a DynamoDB
resource if you're going to instantiate this class
directly, i.e
``boto3.resource('dynamodb').Table('foo').meta.client``.
:type flush_amount: int
:param flush_amount: The number of items to keep in
a local buffer before sending a batch_write_item
request to DynamoDB.
:type overwrite_by_pkeys: list(string)
:param overwrite_by_pkeys: De-duplicate request items in buffer
if match new request item on specified primary keys. i.e
``["partition_key1", "sort_key2", "sort_key3"]``
"""
self._table_name = table_name
self._client = client
self._items_buffer = []
self._flush_amount = flush_amount
self._overwrite_by_pkeys = overwrite_by_pkeys
def put_item(self, Item):
self._add_request_and_process({'PutRequest': {'Item': Item}})
def delete_item(self, Key):
self._add_request_and_process({'DeleteRequest': {'Key': Key}})
def _add_request_and_process(self, request):
if self._overwrite_by_pkeys:
self._remove_dup_pkeys_request_if_any(request)
self._items_buffer.append(request)
self._flush_if_needed()
def _remove_dup_pkeys_request_if_any(self, request):
pkey_values_new = self._extract_pkey_values(request)
for item in self._items_buffer:
if self._extract_pkey_values(item) == pkey_values_new:
self._items_buffer.remove(item)
logger.debug(
"With overwrite_by_pkeys enabled, skipping " "request:%s",
item,
)
def _extract_pkey_values(self, request):
if request.get('PutRequest'):
return [
request['PutRequest']['Item'][key]
for key in self._overwrite_by_pkeys
]
elif request.get('DeleteRequest'):
return [
request['DeleteRequest']['Key'][key]
for key in self._overwrite_by_pkeys
]
return None
def _flush_if_needed(self):
if len(self._items_buffer) >= self._flush_amount:
self._flush()
def _flush(self):
items_to_send = self._items_buffer[: self._flush_amount]
self._items_buffer = self._items_buffer[self._flush_amount :]
response = self._client.batch_write_item(
RequestItems={self._table_name: items_to_send}
)
unprocessed_items = response['UnprocessedItems']
if not unprocessed_items:
unprocessed_items = {}
item_list = unprocessed_items.get(self._table_name, [])
# Any unprocessed_items are immediately added to the
# next batch we send.
self._items_buffer.extend(item_list)
logger.debug(
"Batch write sent %s, unprocessed: %s",
len(items_to_send),
len(self._items_buffer),
)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, tb):
# When we exit, we need to keep flushing whatever's left
# until there's nothing left in our items buffer.
while self._items_buffer:
self._flush()

@ -0,0 +1,343 @@
# Copyright 2015 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
from boto3.compat import collections_abc
from boto3.docs.utils import DocumentModifiedShape
from boto3.dynamodb.conditions import ConditionBase, ConditionExpressionBuilder
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
def register_high_level_interface(base_classes, **kwargs):
base_classes.insert(0, DynamoDBHighLevelResource)
class _ForgetfulDict(dict):
"""A dictionary that discards any items set on it. For use as `memo` in
`copy.deepcopy()` when every instance of a repeated object in the deepcopied
data structure should result in a separate copy.
"""
def __setitem__(self, key, value):
pass
def copy_dynamodb_params(params, **kwargs):
return copy.deepcopy(params, memo=_ForgetfulDict())
class DynamoDBHighLevelResource:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Apply handler that creates a copy of the user provided dynamodb
# item such that it can be modified.
self.meta.client.meta.events.register(
'provide-client-params.dynamodb',
copy_dynamodb_params,
unique_id='dynamodb-create-params-copy',
)
self._injector = TransformationInjector()
# Apply the handler that generates condition expressions including
# placeholders.
self.meta.client.meta.events.register(
'before-parameter-build.dynamodb',
self._injector.inject_condition_expressions,
unique_id='dynamodb-condition-expression',
)
# Apply the handler that serializes the request from python
# types to dynamodb types.
self.meta.client.meta.events.register(
'before-parameter-build.dynamodb',
self._injector.inject_attribute_value_input,
unique_id='dynamodb-attr-value-input',
)
# Apply the handler that deserializes the response from dynamodb
# types to python types.
self.meta.client.meta.events.register(
'after-call.dynamodb',
self._injector.inject_attribute_value_output,
unique_id='dynamodb-attr-value-output',
)
# Apply the documentation customizations to account for
# the transformations.
attr_value_shape_docs = DocumentModifiedShape(
'AttributeValue',
new_type='valid DynamoDB type',
new_description=(
'- The value of the attribute. The valid value types are '
'listed in the '
':ref:`DynamoDB Reference Guide<ref_valid_dynamodb_types>`.'
),
new_example_value=(
'\'string\'|123|Binary(b\'bytes\')|True|None|set([\'string\'])'
'|set([123])|set([Binary(b\'bytes\')])|[]|{}'
),
)
key_expression_shape_docs = DocumentModifiedShape(
'KeyExpression',
new_type=(
'condition from :py:class:`boto3.dynamodb.conditions.Key` '
'method'
),
new_description=(
'The condition(s) a key(s) must meet. Valid conditions are '
'listed in the '
':ref:`DynamoDB Reference Guide<ref_dynamodb_conditions>`.'
),
new_example_value='Key(\'mykey\').eq(\'myvalue\')',
)
con_expression_shape_docs = DocumentModifiedShape(
'ConditionExpression',
new_type=(
'condition from :py:class:`boto3.dynamodb.conditions.Attr` '
'method'
),
new_description=(
'The condition(s) an attribute(s) must meet. Valid conditions '
'are listed in the '
':ref:`DynamoDB Reference Guide<ref_dynamodb_conditions>`.'
),
new_example_value='Attr(\'myattribute\').eq(\'myvalue\')',
)
self.meta.client.meta.events.register(
'docs.*.dynamodb.*.complete-section',
attr_value_shape_docs.replace_documentation_for_matching_shape,
unique_id='dynamodb-attr-value-docs',
)
self.meta.client.meta.events.register(
'docs.*.dynamodb.*.complete-section',
key_expression_shape_docs.replace_documentation_for_matching_shape,
unique_id='dynamodb-key-expression-docs',
)
self.meta.client.meta.events.register(
'docs.*.dynamodb.*.complete-section',
con_expression_shape_docs.replace_documentation_for_matching_shape,
unique_id='dynamodb-cond-expression-docs',
)
class TransformationInjector:
"""Injects the transformations into the user provided parameters."""
def __init__(
self,
transformer=None,
condition_builder=None,
serializer=None,
deserializer=None,
):
self._transformer = transformer
if transformer is None:
self._transformer = ParameterTransformer()
self._condition_builder = condition_builder
if condition_builder is None:
self._condition_builder = ConditionExpressionBuilder()
self._serializer = serializer
if serializer is None:
self._serializer = TypeSerializer()
self._deserializer = deserializer
if deserializer is None:
self._deserializer = TypeDeserializer()
def inject_condition_expressions(self, params, model, **kwargs):
"""Injects the condition expression transformation into the parameters
This injection includes transformations for ConditionExpression shapes
and KeyExpression shapes. It also handles any placeholder names and
values that are generated when transforming the condition expressions.
"""
self._condition_builder.reset()
generated_names = {}
generated_values = {}
# Create and apply the Condition Expression transformation.
transformation = ConditionExpressionTransformation(
self._condition_builder,
placeholder_names=generated_names,
placeholder_values=generated_values,
is_key_condition=False,
)
self._transformer.transform(
params, model.input_shape, transformation, 'ConditionExpression'
)
# Create and apply the Key Condition Expression transformation.
transformation = ConditionExpressionTransformation(
self._condition_builder,
placeholder_names=generated_names,
placeholder_values=generated_values,
is_key_condition=True,
)
self._transformer.transform(
params, model.input_shape, transformation, 'KeyExpression'
)
expr_attr_names_input = 'ExpressionAttributeNames'
expr_attr_values_input = 'ExpressionAttributeValues'
# Now that all of the condition expression transformation are done,
# update the placeholder dictionaries in the request.
if expr_attr_names_input in params:
params[expr_attr_names_input].update(generated_names)
else:
if generated_names:
params[expr_attr_names_input] = generated_names
if expr_attr_values_input in params:
params[expr_attr_values_input].update(generated_values)
else:
if generated_values:
params[expr_attr_values_input] = generated_values
def inject_attribute_value_input(self, params, model, **kwargs):
"""Injects DynamoDB serialization into parameter input"""
self._transformer.transform(
params,
model.input_shape,
self._serializer.serialize,
'AttributeValue',
)
def inject_attribute_value_output(self, parsed, model, **kwargs):
"""Injects DynamoDB deserialization into responses"""
if model.output_shape is not None:
self._transformer.transform(
parsed,
model.output_shape,
self._deserializer.deserialize,
'AttributeValue',
)
class ConditionExpressionTransformation:
"""Provides a transformation for condition expressions
The ``ParameterTransformer`` class can call this class directly
to transform the condition expressions in the parameters provided.
"""
def __init__(
self,
condition_builder,
placeholder_names,
placeholder_values,
is_key_condition=False,
):
self._condition_builder = condition_builder
self._placeholder_names = placeholder_names
self._placeholder_values = placeholder_values
self._is_key_condition = is_key_condition
def __call__(self, value):
if isinstance(value, ConditionBase):
# Create a conditional expression string with placeholders
# for the provided condition.
built_expression = self._condition_builder.build_expression(
value, is_key_condition=self._is_key_condition
)
self._placeholder_names.update(
built_expression.attribute_name_placeholders
)
self._placeholder_values.update(
built_expression.attribute_value_placeholders
)
return built_expression.condition_expression
# Use the user provided value if it is not a ConditonBase object.
return value
class ParameterTransformer:
"""Transforms the input to and output from botocore based on shape"""
def transform(self, params, model, transformation, target_shape):
"""Transforms the dynamodb input to or output from botocore
It applies a specified transformation whenever a specific shape name
is encountered while traversing the parameters in the dictionary.
:param params: The parameters structure to transform.
:param model: The operation model.
:param transformation: The function to apply the parameter
:param target_shape: The name of the shape to apply the
transformation to
"""
self._transform_parameters(model, params, transformation, target_shape)
def _transform_parameters(
self, model, params, transformation, target_shape
):
type_name = model.type_name
if type_name in ('structure', 'map', 'list'):
getattr(self, f'_transform_{type_name}')(
model, params, transformation, target_shape
)
def _transform_structure(
self, model, params, transformation, target_shape
):
if not isinstance(params, collections_abc.Mapping):
return
for param in params:
if param in model.members:
member_model = model.members[param]
member_shape = member_model.name
if member_shape == target_shape:
params[param] = transformation(params[param])
else:
self._transform_parameters(
member_model,
params[param],
transformation,
target_shape,
)
def _transform_map(self, model, params, transformation, target_shape):
if not isinstance(params, collections_abc.Mapping):
return
value_model = model.value
value_shape = value_model.name
for key, value in params.items():
if value_shape == target_shape:
params[key] = transformation(value)
else:
self._transform_parameters(
value_model, params[key], transformation, target_shape
)
def _transform_list(self, model, params, transformation, target_shape):
if not isinstance(params, collections_abc.MutableSequence):
return
member_model = model.member
member_shape = member_model.name
for i, item in enumerate(params):
if member_shape == target_shape:
params[i] = transformation(item)
else:
self._transform_parameters(
member_model, params[i], transformation, target_shape
)

@ -0,0 +1,310 @@
# Copyright 2015 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.
from decimal import (
Clamped,
Context,
Decimal,
Inexact,
Overflow,
Rounded,
Underflow,
)
from boto3.compat import collections_abc
STRING = 'S'
NUMBER = 'N'
BINARY = 'B'
STRING_SET = 'SS'
NUMBER_SET = 'NS'
BINARY_SET = 'BS'
NULL = 'NULL'
BOOLEAN = 'BOOL'
MAP = 'M'
LIST = 'L'
DYNAMODB_CONTEXT = Context(
Emin=-128,
Emax=126,
prec=38,
traps=[Clamped, Overflow, Inexact, Rounded, Underflow],
)
BINARY_TYPES = (bytearray, bytes)
class Binary:
"""A class for representing Binary in dynamodb
Especially for Python 2, use this class to explicitly specify
binary data for item in DynamoDB. It is essentially a wrapper around
binary. Unicode and Python 3 string types are not allowed.
"""
def __init__(self, value):
if not isinstance(value, BINARY_TYPES):
types = ', '.join([str(t) for t in BINARY_TYPES])
raise TypeError(f'Value must be of the following types: {types}')
self.value = value
def __eq__(self, other):
if isinstance(other, Binary):
return self.value == other.value
return self.value == other
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return f'Binary({self.value!r})'
def __str__(self):
return self.value
def __bytes__(self):
return self.value
def __hash__(self):
return hash(self.value)
class TypeSerializer:
"""This class serializes Python data types to DynamoDB types."""
def serialize(self, value):
"""The method to serialize the Python data types.
:param value: A python value to be serialized to DynamoDB. Here are
the various conversions:
Python DynamoDB
------ --------
None {'NULL': True}
True/False {'BOOL': True/False}
int/Decimal {'N': str(value)}
string {'S': string}
Binary/bytearray/bytes (py3 only) {'B': bytes}
set([int/Decimal]) {'NS': [str(value)]}
set([string]) {'SS': [string])
set([Binary/bytearray/bytes]) {'BS': [bytes]}
list {'L': list}
dict {'M': dict}
For types that involve numbers, it is recommended that ``Decimal``
objects are used to be able to round-trip the Python type.
For types that involve binary, it is recommended that ``Binary``
objects are used to be able to round-trip the Python type.
:rtype: dict
:returns: A dictionary that represents a dynamoDB data type. These
dictionaries can be directly passed to botocore methods.
"""
dynamodb_type = self._get_dynamodb_type(value)
serializer = getattr(self, f'_serialize_{dynamodb_type}'.lower())
return {dynamodb_type: serializer(value)}
def _get_dynamodb_type(self, value):
dynamodb_type = None
if self._is_null(value):
dynamodb_type = NULL
elif self._is_boolean(value):
dynamodb_type = BOOLEAN
elif self._is_number(value):
dynamodb_type = NUMBER
elif self._is_string(value):
dynamodb_type = STRING
elif self._is_binary(value):
dynamodb_type = BINARY
elif self._is_type_set(value, self._is_number):
dynamodb_type = NUMBER_SET
elif self._is_type_set(value, self._is_string):
dynamodb_type = STRING_SET
elif self._is_type_set(value, self._is_binary):
dynamodb_type = BINARY_SET
elif self._is_map(value):
dynamodb_type = MAP
elif self._is_listlike(value):
dynamodb_type = LIST
else:
msg = f'Unsupported type "{type(value)}" for value "{value}"'
raise TypeError(msg)
return dynamodb_type
def _is_null(self, value):
if value is None:
return True
return False
def _is_boolean(self, value):
if isinstance(value, bool):
return True
return False
def _is_number(self, value):
if isinstance(value, (int, Decimal)):
return True
elif isinstance(value, float):
raise TypeError(
'Float types are not supported. Use Decimal types instead.'
)
return False
def _is_string(self, value):
if isinstance(value, str):
return True
return False
def _is_binary(self, value):
if isinstance(value, (Binary, bytearray, bytes)):
return True
return False
def _is_set(self, value):
if isinstance(value, collections_abc.Set):
return True
return False
def _is_type_set(self, value, type_validator):
if self._is_set(value):
if False not in map(type_validator, value):
return True
return False
def _is_map(self, value):
if isinstance(value, collections_abc.Mapping):
return True
return False
def _is_listlike(self, value):
if isinstance(value, (list, tuple)):
return True
return False
def _serialize_null(self, value):
return True
def _serialize_bool(self, value):
return value
def _serialize_n(self, value):
number = str(DYNAMODB_CONTEXT.create_decimal(value))
if number in ['Infinity', 'NaN']:
raise TypeError('Infinity and NaN not supported')
return number
def _serialize_s(self, value):
return value
def _serialize_b(self, value):
if isinstance(value, Binary):
value = value.value
return value
def _serialize_ss(self, value):
return [self._serialize_s(s) for s in value]
def _serialize_ns(self, value):
return [self._serialize_n(n) for n in value]
def _serialize_bs(self, value):
return [self._serialize_b(b) for b in value]
def _serialize_l(self, value):
return [self.serialize(v) for v in value]
def _serialize_m(self, value):
return {k: self.serialize(v) for k, v in value.items()}
class TypeDeserializer:
"""This class deserializes DynamoDB types to Python types."""
def deserialize(self, value):
"""The method to deserialize the DynamoDB data types.
:param value: A DynamoDB value to be deserialized to a pythonic value.
Here are the various conversions:
DynamoDB Python
-------- ------
{'NULL': True} None
{'BOOL': True/False} True/False
{'N': str(value)} Decimal(str(value))
{'S': string} string
{'B': bytes} Binary(bytes)
{'NS': [str(value)]} set([Decimal(str(value))])
{'SS': [string]} set([string])
{'BS': [bytes]} set([bytes])
{'L': list} list
{'M': dict} dict
:returns: The pythonic value of the DynamoDB type.
"""
if not value:
raise TypeError(
'Value must be a nonempty dictionary whose key '
'is a valid dynamodb type.'
)
dynamodb_type = list(value.keys())[0]
try:
deserializer = getattr(
self, f'_deserialize_{dynamodb_type}'.lower()
)
except AttributeError:
raise TypeError(f'Dynamodb type {dynamodb_type} is not supported')
return deserializer(value[dynamodb_type])
def _deserialize_null(self, value):
return None
def _deserialize_bool(self, value):
return value
def _deserialize_n(self, value):
return DYNAMODB_CONTEXT.create_decimal(value)
def _deserialize_s(self, value):
return value
def _deserialize_b(self, value):
return Binary(value)
def _deserialize_ns(self, value):
return set(map(self._deserialize_n, value))
def _deserialize_ss(self, value):
return set(map(self._deserialize_s, value))
def _deserialize_bs(self, value):
return set(map(self._deserialize_b, value))
def _deserialize_l(self, value):
return [self.deserialize(v) for v in value]
def _deserialize_m(self, value):
return {k: self.deserialize(v) for k, v in value.items()}

@ -0,0 +1,12 @@
# Copyright 2015 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.

@ -0,0 +1,40 @@
# Copyright 2015 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.
def inject_create_tags(event_name, class_attributes, **kwargs):
"""This injects a custom create_tags method onto the ec2 service resource
This is needed because the resource model is not able to express
creating multiple tag resources based on the fact you can apply a set
of tags to multiple ec2 resources.
"""
class_attributes['create_tags'] = create_tags
def create_tags(self, **kwargs):
# Call the client method
self.meta.client.create_tags(**kwargs)
resources = kwargs.get('Resources', [])
tags = kwargs.get('Tags', [])
tag_resources = []
# Generate all of the tag resources that just were created with the
# preceding client call.
for resource in resources:
for tag in tags:
# Add each tag from the tag set for each resource to the list
# that is returned by the method.
tag_resource = self.Tag(resource, tag['Key'], tag['Value'])
tag_resources.append(tag_resource)
return tag_resources

@ -0,0 +1,37 @@
# Copyright 2016 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.
from boto3.resources.action import CustomModeledAction
def inject_delete_tags(event_emitter, **kwargs):
action_model = {
'request': {
'operation': 'DeleteTags',
'params': [
{
'target': 'Resources[0]',
'source': 'identifier',
'name': 'Id',
}
],
}
}
action = CustomModeledAction(
'delete_tags', action_model, delete_tags, event_emitter
)
action.inject(**kwargs)
def delete_tags(self, **kwargs):
kwargs['Resources'] = [self.id]
return self.meta.client.delete_tags(**kwargs)

@ -0,0 +1,35 @@
Generate a signed URL for Amazon CloudFront
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following example shows how to generate a signed URL for Amazon CloudFront.
Note that you will need the ``cryptography`` `library <https://cryptography.io/en/latest/>`__ to follow this example::
import datetime
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
from botocore.signers import CloudFrontSigner
def rsa_signer(message):
with open('path/to/key.pem', 'rb') as key_file:
private_key = serialization.load_pem_private_key(
key_file.read(),
password=None,
backend=default_backend()
)
return private_key.sign(message, padding.PKCS1v15(), hashes.SHA1())
key_id = 'AKIAIOSFODNN7EXAMPLE'
url = 'http://d2949o5mkkp72v.cloudfront.net/hello.txt'
expire_date = datetime.datetime(2017, 1, 1)
cloudfront_signer = CloudFrontSigner(key_id, rsa_signer)
# Create a signed url that will be valid until the specific expiry date
# provided using a canned policy.
signed_url = cloudfront_signer.generate_presigned_url(
url, date_less_than=expire_date)
print(signed_url)

@ -0,0 +1,185 @@
List objects in an Amazon S3 bucket
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following example shows how to use an Amazon S3 bucket resource to list
the objects in the bucket.
.. code-block:: python
import boto3
s3 = boto3.resource('s3')
bucket = s3.Bucket('my-bucket')
for obj in bucket.objects.all():
print(obj.key)
List top-level common prefixes in Amazon S3 bucket
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This example shows how to list all of the top-level common prefixes in an
Amazon S3 bucket:
.. code-block:: python
import boto3
client = boto3.client('s3')
paginator = client.get_paginator('list_objects')
result = paginator.paginate(Bucket='my-bucket', Delimiter='/')
for prefix in result.search('CommonPrefixes'):
print(prefix.get('Prefix'))
Restore Glacier objects in an Amazon S3 bucket
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following example shows how to initiate restoration of glacier objects in
an Amazon S3 bucket, determine if a restoration is on-going, and determine if a
restoration is finished.
.. code-block:: python
import boto3
s3 = boto3.resource('s3')
bucket = s3.Bucket('glacier-bucket')
for obj_sum in bucket.objects.all():
obj = s3.Object(obj_sum.bucket_name, obj_sum.key)
if obj.storage_class == 'GLACIER':
# Try to restore the object if the storage class is glacier and
# the object does not have a completed or ongoing restoration
# request.
if obj.restore is None:
print('Submitting restoration request: %s' % obj.key)
obj.restore_object(RestoreRequest={'Days': 1})
# Print out objects whose restoration is on-going
elif 'ongoing-request="true"' in obj.restore:
print('Restoration in-progress: %s' % obj.key)
# Print out objects whose restoration is complete
elif 'ongoing-request="false"' in obj.restore:
print('Restoration complete: %s' % obj.key)
Uploading/downloading files using SSE KMS
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This example shows how to use SSE-KMS to upload objects using
server side encryption with a key managed by KMS.
We can either use the default KMS master key, or create a
custom key in AWS and use it to encrypt the object by passing in its
key id.
With KMS, nothing else needs to be provided for getting the
object; S3 already knows how to decrypt the object.
.. code-block:: python
import boto3
import os
BUCKET = 'your-bucket-name'
s3 = boto3.client('s3')
keyid = '<the key id>'
print("Uploading S3 object with SSE-KMS")
s3.put_object(Bucket=BUCKET,
Key='encrypt-key',
Body=b'foobar',
ServerSideEncryption='aws:kms',
# Optional: SSEKMSKeyId
SSEKMSKeyId=keyid)
print("Done")
# Getting the object:
print("Getting S3 object...")
response = s3.get_object(Bucket=BUCKET,
Key='encrypt-key')
print("Done, response body:")
print(response['Body'].read())
Uploading/downloading files using SSE Customer Keys
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This example shows how to use SSE-C to upload objects using
server side encryption with a customer provided key.
First, we'll need a 32 byte key. For this example, we'll
randomly generate a key but you can use any 32 byte key
you want. Remember, you must the same key to download
the object. If you lose the encryption key, you lose
the object.
Also note how we don't have to provide the SSECustomerKeyMD5.
Boto3 will automatically compute this value for us.
.. code-block:: python
import boto3
import os
BUCKET = 'your-bucket-name'
KEY = os.urandom(32)
s3 = boto3.client('s3')
print("Uploading S3 object with SSE-C")
s3.put_object(Bucket=BUCKET,
Key='encrypt-key',
Body=b'foobar',
SSECustomerKey=KEY,
SSECustomerAlgorithm='AES256')
print("Done")
# Getting the object:
print("Getting S3 object...")
# Note how we're using the same ``KEY`` we
# created earlier.
response = s3.get_object(Bucket=BUCKET,
Key='encrypt-key',
SSECustomerKey=KEY,
SSECustomerAlgorithm='AES256')
print("Done, response body:")
print(response['Body'].read())
Downloading a specific version of an S3 object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This example shows how to download a specific version of an
S3 object.
.. code-block:: python
import boto3
s3 = boto3.client('s3')
s3.download_file(
"bucket-name", "key-name", "tmp.txt",
ExtraArgs={"VersionId": "my-version-id"}
)
Filter objects by last modified time using JMESPath
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This example shows how to filter objects by last modified time
using JMESPath.
.. code-block:: python
import boto3
s3 = boto3.client("s3")
s3_paginator = s3.get_paginator('list_objects_v2')
s3_iterator = s3_paginator.paginate(Bucket='your-bucket-name')
filtered_iterator = s3_iterator.search(
"Contents[?to_string(LastModified)>='\"2022-01-05 08:05:37+00:00\"'].Key"
)
for key_data in filtered_iterator:
print(key_data)

@ -0,0 +1,126 @@
# 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.
# All exceptions in this class should subclass from Boto3Error.
import botocore.exceptions
# All exceptions should subclass from Boto3Error in this module.
class Boto3Error(Exception):
"""Base class for all Boto3 errors."""
class ResourceLoadException(Boto3Error):
pass
# NOTE: This doesn't appear to be used anywhere.
# It's probably safe to remove this.
class NoVersionFound(Boto3Error):
pass
# We're subclassing from botocore.exceptions.DataNotFoundError
# to keep backwards compatibility with anyone that was catching
# this low level Botocore error before this exception was
# introduced in boto3.
# Same thing for ResourceNotExistsError below.
class UnknownAPIVersionError(
Boto3Error, botocore.exceptions.DataNotFoundError
):
def __init__(self, service_name, bad_api_version, available_api_versions):
msg = (
f"The '{service_name}' resource does not support an API version of: {bad_api_version}\n"
f"Valid API versions are: {available_api_versions}"
)
# Not using super because we don't want the DataNotFoundError
# to be called, it has a different __init__ signature.
Boto3Error.__init__(self, msg)
class ResourceNotExistsError(
Boto3Error, botocore.exceptions.DataNotFoundError
):
"""Raised when you attempt to create a resource that does not exist."""
def __init__(self, service_name, available_services, has_low_level_client):
msg = (
"The '{}' resource does not exist.\n"
"The available resources are:\n"
" - {}\n".format(
service_name, '\n - '.join(available_services)
)
)
if has_low_level_client:
msg = (
f"{msg}\nConsider using a boto3.client('{service_name}') "
f"instead of a resource for '{service_name}'"
)
# Not using super because we don't want the DataNotFoundError
# to be called, it has a different __init__ signature.
Boto3Error.__init__(self, msg)
class RetriesExceededError(Boto3Error):
def __init__(self, last_exception, msg='Max Retries Exceeded'):
super().__init__(msg)
self.last_exception = last_exception
class S3TransferFailedError(Boto3Error):
pass
class S3UploadFailedError(Boto3Error):
pass
class DynamoDBOperationNotSupportedError(Boto3Error):
"""Raised for operations that are not supported for an operand."""
def __init__(self, operation, value):
msg = (
f'{operation} operation cannot be applied to value {value} of type '
f'{type(value)} directly. Must use AttributeBase object methods '
f'(i.e. Attr().eq()). to generate ConditionBase instances first.'
)
Exception.__init__(self, msg)
# FIXME: Backward compatibility
DynanmoDBOperationNotSupportedError = DynamoDBOperationNotSupportedError
class DynamoDBNeedsConditionError(Boto3Error):
"""Raised when input is not a condition"""
def __init__(self, value):
msg = (
f'Expecting a ConditionBase object. Got {value} of type {type(value)}. '
f'Use AttributeBase object methods (i.e. Attr().eq()). to '
f'generate ConditionBase instances.'
)
Exception.__init__(self, msg)
class DynamoDBNeedsKeyConditionError(Boto3Error):
pass
class PythonDeprecationWarning(Warning):
"""
Python version being used is scheduled to become unsupported
in an future release. See warning for specifics.
"""
pass

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

@ -0,0 +1,155 @@
# 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 'ResourceMeta(\'{}\', identifiers={})'.format(
self.service_name, 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,572 @@
# 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,
'{}.{}'.format(
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,
'{}.{}'.format(
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 = '{}.{}Collection'.format(
service_context.service_name, collection_name
)
else:
cls_name = '{}.{}.{}Collection'.format(
service_context.service_name, resource_name, collection_name
)
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

@ -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

@ -0,0 +1,632 @@
# 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(
'Problem renaming {} {} to {}!'.format(
self.name, category, 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

@ -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]

@ -0,0 +1,318 @@
# 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(
'Search path hits shape type {} from {}'.format(
shape.type_name, 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

@ -0,0 +1,12 @@
# Copyright 2015 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.

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save

Powered by BW's shoe-string budget.