summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrey Kislyuk <kislyuk@gmail.com>2017-01-12 11:20:47 -0800
committerAndrey Kislyuk <kislyuk@gmail.com>2017-01-12 11:20:47 -0800
commitd157bbccb7a0777683af428061bf24b8949ad479 (patch)
tree0593f68b9e41d3c7ff7ddb4dcbba125ddf7a79b7
Initial commit
-rw-r--r--.gitignore35
-rw-r--r--.travis.yml25
-rw-r--r--Changes.rst3
-rw-r--r--LICENSE191
-rw-r--r--Makefile27
-rw-r--r--README.rst51
-rw-r--r--common.mk45
-rwxr-xr-xddbcli/__init__.py98
-rwxr-xr-xscripts/ddb4
-rw-r--r--setup.cfg5
-rwxr-xr-xsetup.py37
11 files changed, 521 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..958826b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,35 @@
+# Reminder:
+# - A leading slash means the pattern is anchored at the root.
+# - No leading slash means the pattern matches at any depth.
+
+# Python files
+*.pyc
+__pycache__/
+.tox/
+*.egg-info/
+/build/
+/dist/
+/.eggs/
+
+# Sphinx documentation
+/docs/_build/
+
+# IDE project files
+/.pydevproject
+
+# vim python-mode plugin
+/.ropeproject
+
+# IntelliJ IDEA / PyCharm project files
+/.idea
+/*.iml
+
+# JS/node/npm/web dev files
+node_modules
+npm-debug.log
+
+# OS X metadata files
+.DS_Store
+
+# Python coverage
+.coverage
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..14b4043
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,25 @@
+language: python
+python:
+ - 2.7
+ - 3.3
+ - 3.4
+ - 3.5
+ - pypy
+ - pypy3
+
+before_install:
+ - pip install --quiet coverage flake8
+
+install:
+ - python setup.py install
+
+script:
+ - make test
+ - coverage run --omit='*/site-packages/*,*/distutils/*' ./test/test.py
+ - coverage report --show-missing
+
+after_success:
+ - pip install --quiet coveralls
+ - coveralls
+
+sudo: false
diff --git a/Changes.rst b/Changes.rst
new file mode 100644
index 0000000..22a2000
--- /dev/null
+++ b/Changes.rst
@@ -0,0 +1,3 @@
+Version 0.0.1 (2017-01-12)
+--------------------------
+- Initial release.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+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:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+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
+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
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License 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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..08d0322
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,27 @@
+test_deps:
+ pip install coverage flake8 wheel
+
+lint: test_deps
+ ./setup.py flake8
+
+test: test_deps lint
+ coverage run --source=$$(python setup.py --name) ./test/test.py
+
+init_docs:
+ cd docs; sphinx-quickstart
+
+docs:
+ $(MAKE) -C docs html
+
+install: clean
+ pip install wheel
+ python setup.py bdist_wheel
+ pip install --upgrade dist/*.whl
+
+clean:
+ -rm -rf build dist
+ -rm -rf *.egg-info
+
+.PHONY: lint test test_deps docs install clean
+
+include common.mk
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..7f6d284
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,51 @@
+DDBCLI: A DynamoDB command line interface with JSON I/O
+=======================================================
+
+Installation
+------------
+::
+
+ pip install ddbcli
+
+Synopsis
+--------
+
+Use ``aws configure`` to set up your AWS command line environment.
+
+.. code-block:: bash
+
+ ddb get TABLE_NAME HASH_KEY
+ DYNAMODB_TABLE=mytable ddb get HASH_KEY
+
+ ddb put mytable '{"key": "foo", "data": "xyz"}' '{"key": "bar", "data": "xyz"}'
+ ddb scan mytable
+
+Authors
+-------
+* Andrey Kislyuk
+
+Links
+-----
+* `Project home page (GitHub) <https://github.com/XML-Security/ddbcli>`_
+* `Documentation (Read the Docs) <https://ddbcli.readthedocs.io/en/latest/>`_
+* `Package distribution (PyPI) <https://pypi.python.org/pypi/ddbcli>`_
+* `Change log <https://github.com/XML-Security/ddbcli/blob/master/Changes.rst>`_
+
+Bugs
+~~~~
+Please report bugs, issues, feature requests, etc. on `GitHub <https://github.com/XML-Security/ddbcli/issues>`_.
+
+License
+-------
+Licensed under the terms of the `Apache License, Version 2.0 <http://www.apache.org/licenses/LICENSE-2.0>`_.
+
+.. image:: https://img.shields.io/travis/XML-Security/ddbcli.svg
+ :target: https://travis-ci.org/XML-Security/ddbcli
+.. image:: https://codecov.io/github/XML-Security/ddbcli/coverage.svg?branch=master
+ :target: https://codecov.io/github/XML-Security/ddbcli?branch=master
+.. image:: https://img.shields.io/pypi/v/ddbcli.svg
+ :target: https://pypi.python.org/pypi/ddbcli
+.. image:: https://img.shields.io/pypi/l/ddbcli.svg
+ :target: https://pypi.python.org/pypi/ddbcli
+.. image:: https://readthedocs.org/projects/ddbcli/badge/?version=latest
+ :target: https://ddbcli.readthedocs.io/
diff --git a/common.mk b/common.mk
new file mode 100644
index 0000000..2c1350a
--- /dev/null
+++ b/common.mk
@@ -0,0 +1,45 @@
+SHELL=/bin/bash -eo pipefail
+
+release_major:
+ $(eval export TAG=$(shell git describe --tags --match 'v*.*.*' | perl -ne '/^v(\d)+\.(\d)+\.(\d+)+/; print "v@{[$$1+1]}.0.0"'))
+ $(MAKE) release
+
+release_minor:
+ $(eval export TAG=$(shell git describe --tags --match 'v*.*.*' | perl -ne '/^v(\d)+\.(\d)+\.(\d+)+/; print "v$$1.@{[$$2+1]}.0"'))
+ $(MAKE) release
+
+release_patch:
+ $(eval export TAG=$(shell git describe --tags --match 'v*.*.*' | perl -ne '/^v(\d)+\.(\d)+\.(\d+)+/; print "v$$1.$$2.@{[$$3+1]}"'))
+ $(MAKE) release
+
+release:
+ @if [[ -z $$TAG ]]; then echo "Use release_{major,minor,patch}"; exit 1; fi
+ $(eval REMOTE=$(shell git remote get-url origin | perl -ne '/([^\/\:]+\/.+?)(\.git)?$$/; print $$1'))
+ $(eval GIT_USER=$(shell git config --get user.email))
+ $(eval GH_AUTH=$(shell if grep -q '@github.com' ~/.git-credentials; then echo $$(grep '@github.com' ~/.git-credentials | python3 -c 'import sys, urllib.parse as p; print(p.urlparse(sys.stdin.read()).netloc.split("@")[0])'); else echo $(GIT_USER); fi))
+ $(eval RELEASES_API=https://api.github.com/repos/${REMOTE}/releases)
+ $(eval UPLOADS_API=https://uploads.github.com/repos/${REMOTE}/releases)
+ git pull
+ git clean -x --force $$(python setup.py --name)
+ sed -i -e "s/version=\([\'\"]\)[0-9]\+\.[0-9]\+\.[0-9]\+/version=\1$${TAG:1}/" setup.py
+ git add setup.py
+ TAG_MSG=$$(mktemp); \
+ echo "# Changes for ${TAG} ($$(date +%Y-%m-%d))" > $$TAG_MSG; \
+ git log --pretty=format:%s $$(git describe --abbrev=0)..HEAD >> $$TAG_MSG; \
+ $${EDITOR:-emacs} $$TAG_MSG; \
+ if [[ -f Changes.md ]]; then cat $$TAG_MSG <(echo) Changes.md | sponge Changes.md; git add Changes.md; fi; \
+ if [[ -f Changes.rst ]]; then cat <(pandoc --from markdown --to rst $$TAG_MSG) <(echo) Changes.rst | sponge Changes.rst; git add Changes.rst; fi; \
+ git commit -m ${TAG}; \
+ git tag --sign --annotate --file $$TAG_MSG ${TAG}
+ git push --follow-tags
+ http --auth ${GH_AUTH} ${RELEASES_API} tag_name=${TAG} name=${TAG} \
+ body="$$(git tag --list ${TAG} -n99 | perl -pe 's/^\S+\s*// if $$. == 1' | sed 's/^\s\s\s\s//')"
+ $(MAKE) install
+ http --auth ${GH_AUTH} POST ${UPLOADS_API}/$$(http --auth ${GH_AUTH} ${RELEASES_API}/latest | jq .id)/assets \
+ name==$$(basename dist/*.whl) label=="Python Wheel" < dist/*.whl
+ $(MAKE) pypi_release
+
+pypi_release:
+ python setup.py sdist bdist_wheel upload --sign
+
+.PHONY: release
diff --git a/ddbcli/__init__.py b/ddbcli/__init__.py
new file mode 100755
index 0000000..7b36e3e
--- /dev/null
+++ b/ddbcli/__init__.py
@@ -0,0 +1,98 @@
+"""
+ddbcli: DynamoDB Command Line Interface
+"""
+
+from __future__ import absolute_import, division, print_function, unicode_literals
+
+import os, sys, argparse, logging, shutil, json, datetime, traceback, errno
+import boto3
+from botocore.exceptions import NoRegionError
+from tweak import Config
+
+logger = logging.getLogger(__name__)
+config = Config("ddbcli")
+parser = argparse.ArgumentParser(description=__doc__)
+parser.add_argument("--log-level", type=logger.setLevel,
+ help=str([logging.getLevelName(i) for i in range(0, 60, 10)]),
+ default=logging.WARN)
+subparsers = parser.add_subparsers()
+
+def register_parser(function, **add_parser_args):
+ subparser = subparsers.add_parser(function.__name__, **add_parser_args)
+ subparser.add_argument("table")
+ subparser.set_defaults(entry_point=function)
+ if subparser.description is None:
+ subparser.description = add_parser_args.get("help", function.__doc__)
+ return subparser
+
+def main(args=None):
+ if args is None:
+ args = sys.argv[1:]
+ if "DYNAMODB_TABLE" in os.environ:
+ args.insert(1, os.environ["DYNAMODB_TABLE"])
+ parsed_args = parser.parse_args(args=args)
+ try:
+ result = parsed_args.entry_point(parsed_args)
+ except Exception as e:
+ if isinstance(e, NoRegionError):
+ msg = "The AWS CLI is not configured."
+ msg += " Please configure it using instructions at"
+ msg += " http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html"
+ exit(msg)
+ elif logger.level < logging.ERROR:
+ raise
+ else:
+ err_msg = traceback.format_exc()
+ try:
+ err_log_filename = os.path.join(config.user_config_dir, "error.log")
+ with open(err_log_filename, "ab") as fh:
+ print(datetime.datetime.now().isoformat(), file=fh)
+ print(err_msg, file=fh)
+ exit("{}: {}. See {} for error details.".format(e.__class__.__name__, e, err_log_filename))
+ except Exception:
+ print(err_msg, file=sys.stderr)
+ exit(os.EX_SOFTWARE)
+ if isinstance(result, SystemExit):
+ raise result
+ elif result is not None:
+ if isinstance(result, dict) and "ResponseMetadata" in result:
+ del result["ResponseMetadata"]
+ print(json.dumps(result, indent=2, default=lambda x: str(x)))
+
+def get_key_schema(table):
+ if "key_schema" not in config:
+ config.key_schema = {}
+ if table.name not in config.key_schema:
+ config.key_schema[table.name] = table.key_schema
+ return config.key_schema[table.name]
+
+def get(args):
+ table = boto3.resource("dynamodb").Table(args.table)
+ key_attr_name = get_key_schema(table)[0]["AttributeName"]
+ return table.get_item(Key={key_attr_name: args.key[0]})["Item"]
+
+parser_get = register_parser(get)
+parser_get.add_argument("key", nargs="*")
+
+def put(args):
+ table = boto3.resource("dynamodb").Table(args.table)
+ with table.batch_writer() as batch:
+ for item in args.items:
+ batch.put_item(Item=item)
+
+parser_put = register_parser(put)
+parser_put.add_argument("items", nargs="*", type=json.loads)
+
+def paginate(boto3_paginator, *args, **kwargs):
+ for page in boto3_paginator.paginate(*args, **kwargs):
+ for result_key in boto3_paginator.result_keys:
+ for value in page.get(result_key.parsed.get("value"), []):
+ yield value
+
+def scan(args):
+ table = boto3.resource("dynamodb").Table(args.table)
+ return table.scan()["Items"]
+
+parser_scan = register_parser(scan)
+
+main()
diff --git a/scripts/ddb b/scripts/ddb
new file mode 100755
index 0000000..b7b4447
--- /dev/null
+++ b/scripts/ddb
@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+from ddbcli import main
+
+main()
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..46f9d22
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[bdist_wheel]
+universal=1
+[flake8]
+max-line-length=120
+ignore: E301, E302, E305, E401, E261, E265, E226, F401, E501
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..526439d
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+import os, glob
+from setuptools import setup, find_packages
+
+setup(
+ name="ddbcli",
+ version="0.0.1",
+ url="https://github.com/kislyuk/ddbcli",
+ license="Apache Software License",
+ author="Andrey Kislyuk",
+ author_email="kislyuk@gmail.com",
+ description="A DynamoDB Command Line Interface with JSON I/O",
+ long_description=open("README.rst").read(),
+ install_requires=[
+ "setuptools",
+ "boto3 >= 1.4.2, < 2",
+ "awscli"
+ ],
+ packages=find_packages(exclude=["test"]),
+ include_package_data=True,
+ platforms=["MacOS X", "Posix"],
+ test_suite="test",
+ classifiers=[
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: Apache Software License",
+ "Operating System :: MacOS :: MacOS X",
+ "Operating System :: POSIX",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3.3",
+ "Programming Language :: Python :: 3.4",
+ "Programming Language :: Python :: 3.5",
+ "Programming Language :: Python :: 3.6",
+ "Topic :: Software Development :: Libraries :: Python Modules"
+ ]
+)