[Python-modules-commits] r31311 - in packages (14 files)

bernat at users.alioth.debian.org bernat at users.alioth.debian.org
Thu Oct 30 10:14:54 UTC 2014


    Date: Thursday, October 30, 2014 @ 10:14:54
  Author: bernat
Revision: 31311

[svn-inject] Installing original source of python-cs (0.5.7)

Added:
  packages/python-cs/
  packages/python-cs/branches/
  packages/python-cs/branches/upstream/
  packages/python-cs/branches/upstream/python-cs/
  packages/python-cs/branches/upstream/python-cs/current/
  packages/python-cs/branches/upstream/python-cs/current/.gitignore
  packages/python-cs/branches/upstream/python-cs/current/.travis.yml
  packages/python-cs/branches/upstream/python-cs/current/LICENSE
  packages/python-cs/branches/upstream/python-cs/current/README.rst
  packages/python-cs/branches/upstream/python-cs/current/cs.py
  packages/python-cs/branches/upstream/python-cs/current/setup.cfg
  packages/python-cs/branches/upstream/python-cs/current/setup.py
  packages/python-cs/branches/upstream/python-cs/current/tests.py
  packages/python-cs/branches/upstream/python-cs/current/tox.ini

Added: packages/python-cs/branches/upstream/python-cs/current/.gitignore
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/.gitignore	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/.gitignore	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,4 @@
+cs.egg-info
+dist
+.tox
+build

Added: packages/python-cs/branches/upstream/python-cs/current/.travis.yml
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/.travis.yml	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/.travis.yml	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,14 @@
+language: python
+python: 3.4
+
+env:
+  - TOXENV=py27
+  - TOXENV=py33
+  - TOXENV=py34
+  - TOXENV=lint
+
+install:
+  - pip install tox
+
+script:
+  - tox -e $TOXENV

Added: packages/python-cs/branches/upstream/python-cs/current/LICENSE
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/LICENSE	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/LICENSE	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,27 @@
+Copyright (c) 2014, Bruno Renié and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+this list of conditions and the following disclaimer in the documentation
+and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its contributors
+may be used to endorse or promote products derived from this software without
+specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Added: packages/python-cs/branches/upstream/python-cs/current/README.rst
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/README.rst	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/README.rst	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,80 @@
+CS
+==
+
+.. image:: https://travis-ci.org/exoscale/cs.svg?branch=master
+   :alt: Build Status
+   :target: https://travis-ci.org/exoscale/cs
+
+A simple, yet powerful CloudStack API client for python and the command-line.
+
+* Python 2.7+ and 3.3+ support.
+* All present and future CloudStack API calls and parameters are supported.
+* Syntax highlight in the command-line client if Pygments is installed.
+* BSD license.
+
+Installation
+------------
+
+::
+
+    pip install cs
+
+Usage
+-----
+
+In Python::
+
+    from cs import CloudStack
+
+    cs = CloudStack(endpoint='https://api.exoscale.ch/compute',
+                    key='cloudstack api key',
+                    secret='cloudstack api secret')
+
+    vms = cs.listVirtualMachines()
+
+    cs.createSecurityGroup(name='web', description='HTTP traffic')
+
+From the command-line, this requires some configuration::
+
+    cat $HOME/.cloudstack.ini
+    [cloudstack]
+    endpoint = https://api.exoscale.ch/compute
+    key = cloudstack api key
+    secret = cloudstack api secret
+
+Then::
+
+    $ cs listVirtualMachines
+    {
+      "count": 1,
+      "virtualmachine": [
+        {
+          "account": "...",
+          ...
+        }
+      ]
+    }
+
+    $ cs authorizeSecurityGroupIngress \
+        cidrlist="0.0.0.0/0" endport=443 startport=443 \
+        securitygroupname="blah blah" protocol=tcp
+
+The command-line client polls when async results are returned. To disable
+polling, use the ``--async`` flag.
+
+Configuration is read from several locations, in the following order:
+
+* The ``CLOUDSTACK_ENDPOINT``, ``CLOUDSTACK_KEY`` and ``CLOUDSTACK_SECRET``
+  environment variables,
+* A ``CLOUDSTACK_CONFIG`` environment variable pointing to an ``.ini`` file,
+* A ``cloudstack.ini`` file in the current working directory,
+* A ``.cloudstack.ini`` file in the home directory.
+
+To use that configuration scheme from your Python code::
+
+    from cs import CloudStack, read_config
+
+    cs = CloudStack(**read_config())
+
+Note that ``read_config()`` can raise ``SystemExit`` if no configuration is
+found.

Added: packages/python-cs/branches/upstream/python-cs/current/cs.py
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/cs.py	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/cs.py	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,225 @@
+#! /usr/bin/env python
+
+import base64
+import hashlib
+import hmac
+import json
+import os
+import requests
+import sys
+import time
+
+from collections import defaultdict
+
+try:
+    from configparser import ConfigParser
+except ImportError:  # python 2
+    from ConfigParser import ConfigParser
+
+try:
+    from urllib.parse import quote
+except ImportError:  # python 2
+    from urllib import quote
+
+try:
+    import pygments
+    from pygments.lexers import JsonLexer
+    from pygments.formatters import TerminalFormatter
+except ImportError:
+    pygments = None
+
+
+PY2 = sys.version_info < (3, 0)
+
+if PY2:
+    text_type = unicode  # noqa
+    string_type = basestring  # noqa
+    integer_types = int, long  # noqa
+else:
+    text_type = str
+    string_type = str
+    integer_types = int
+
+
+def cs_encode(value):
+    """
+    Try to behave like cloudstack, which uses
+    java.net.URLEncoder.encode(stuff).replace('+', '%20').
+    """
+    if isinstance(value, int):
+        value = str(value)
+    elif PY2 and isinstance(value, text_type):
+        value = value.encode('utf-8')
+    return quote(value, safe=".-*_")
+
+
+def transform(params):
+    for key, value in list(params.items()):
+        if value is None:
+            params.pop(key)
+            continue
+        if isinstance(value, string_type):
+            continue
+        elif isinstance(value, integer_types):
+            params[key] = text_type(value)
+        elif isinstance(value, (list, tuple, set)):
+            if not value:
+                params.pop(key)
+            else:
+                if isinstance(value, set):
+                    value = list(value)
+                if not isinstance(value[0], dict):
+                    params[key] = ",".join(value)
+                else:
+                    params.pop(key)
+                    for index, val in enumerate(value):
+                        for k, v in val.items():
+                            params["%s[%d].%s" % (key, index, k)] = v
+        else:
+            raise ValueError(type(value))
+    return params
+
+
+class CloudStackException(Exception):
+    pass
+
+
+class Unauthorized(CloudStackException):
+    pass
+
+
+class CloudStack(object):
+    def __init__(self, endpoint, key, secret, timeout=10):
+        self.endpoint = endpoint
+        self.key = key
+        self.secret = secret
+        self.timeout = timeout
+
+    def __repr__(self):
+        return '<CloudStack: {0}>'.format(self.endpoint)
+
+    def __getattr__(self, command):
+        def handler(**kwargs):
+            return self._request(command, **kwargs)
+        return handler
+
+    def _request(self, command, json=True, opcode_name='command', **kwargs):
+        kwargs.update({
+            'apiKey': self.key,
+            opcode_name: command,
+        })
+        if json:
+            kwargs['response'] = 'json'
+        if 'page' in kwargs:
+            kwargs.setdefault('pagesize', 500)
+
+        kwargs = transform(kwargs)
+        kwargs['signature'] = self._sign(kwargs)
+        response = requests.get(self.endpoint, params=kwargs,
+                                timeout=self.timeout)
+        data = response.json()
+        key = '{0}response'.format(command.lower())
+        if response.status_code != 200:
+            raise CloudStackException(
+                "HTTP {0} response from CloudStack".format(
+                    response.status_code), response,
+                data.get(key, data.get('errorresponse')))
+        return data[key]
+
+    def _sign(self, data):
+        """
+        Computes a signature string according to the CloudStack
+        signature method (hmac/sha1).
+        """
+        params = "&".join(sorted([
+            "=".join((key, cs_encode(value))).lower()
+            for key, value in data.items()
+        ]))
+        digest = hmac.new(
+            self.secret.encode('utf-8'),
+            msg=params.encode('utf-8'),
+            digestmod=hashlib.sha1).digest()
+        return base64.b64encode(digest).decode('utf-8').strip()
+
+
+def read_config():
+    # Try env vars first
+    keys = ['endpoint', 'key', 'secret']
+    for key in keys:
+        if 'CLOUDSTACK_{0}'.format(key.upper()) not in os.environ:
+            break
+    else:
+        return {key: os.environ['CLOUDSTACK_{0}'.format(key.upper())]
+                for key in keys}
+
+    # Config file: $PWD/cloudstack.ini or $HOME/.cloudstack.ini
+    # Last read wins in configparser
+    paths = (
+        os.path.join(os.path.expanduser('~'), '.cloudstack.ini'),
+        os.path.join(os.getcwd(), 'cloudstack.ini'),
+    )
+    # Look at CLOUDSTACK_CONFIG first if present
+    if 'CLOUDSTACK_CONFIG' in os.environ:
+        paths += (os.environ['CLOUDSTACK_CONFIG'],)
+    if not any([os.path.exists(c) for c in paths]):
+        raise SystemExit("Config file not found. Tried {0}".format(
+            ", ".join(paths)))
+    conf = ConfigParser()
+    conf.read(paths)
+    try:
+        return conf['cloudstack']
+    except AttributeError:  # python 2
+        return dict(conf.items('cloudstack'))
+
+
+def main():
+    config = read_config()
+    cs = CloudStack(**config)
+
+    usage = "Usage: {0} <command> [option1=value1 " \
+            "[option2=value2] ...] [--async]".format(sys.argv[0])
+
+    if len(sys.argv) == 1:
+        raise SystemExit(usage)
+
+    command = sys.argv[1]
+    kwargs = defaultdict(set)
+    flags = []
+    for option in sys.argv[2:]:
+        if option.startswith('--'):
+            flags.append(option.strip('-'))
+            continue
+        if '=' not in option:
+            raise SystemExit(usage)
+
+        key, value = option.split('=', 1)
+        kwargs[key].add(value.strip(" \"'"))
+
+    try:
+        response = getattr(cs, command)(**kwargs)
+    except CloudStackException as e:
+        response = e.args[2]
+        sys.stderr.write("Cloudstack error:\n")
+
+    if 'Async' not in command and 'jobid' in response and 'async' not in flags:
+        sys.stderr.write("Polling result... ^C to abort\n")
+        while True:
+            try:
+                res = cs.queryAsyncJobResult(**response)
+                if res['jobprocstatus'] == 0:
+                    response = res
+                    break
+                time.sleep(3)
+            except KeyboardInterrupt:
+                sys.stderr.write("Result not ready yet.\n")
+                break
+
+    data = json.dumps(response, indent=2, sort_keys=True)
+
+    if pygments and sys.stdout.isatty():
+        data = pygments.highlight(data, JsonLexer(), TerminalFormatter())
+    sys.stdout.write(data)
+
+
+if __name__ == '__main__':
+    main()

Added: packages/python-cs/branches/upstream/python-cs/current/setup.cfg
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/setup.cfg	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/setup.cfg	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,2 @@
+[wheel]
+universal = 1

Added: packages/python-cs/branches/upstream/python-cs/current/setup.py
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/setup.py	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/setup.py	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,41 @@
+# coding: utf-8
+from setuptools import setup
+
+with open('README.rst', 'r') as f:
+    long_description = f.read()
+
+setup(
+    name='cs',
+    version='0.5.7',
+    url='https://github.com/exoscale/cs',
+    license='BSD',
+    author=u'Bruno Renié',
+    description=('A simple yet powerful CloudStack API client for '
+                 'Python and the command-line.'),
+    long_description=long_description,
+    py_modules=('cs',),
+    zip_safe=False,
+    include_package_data=True,
+    platforms='any',
+    classifiers=(
+        'Intended Audience :: Developers',
+        'Intended Audience :: System Administrators',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Programming Language :: Python :: 3',
+    ),
+    install_requires=(
+        'requests',
+    ),
+    extras_require={
+        'highlight': ['pygments'],
+    },
+    test_suite='tests',
+    entry_points={
+        'console_scripts': [
+            'cs = cs:main',
+        ],
+    },
+)

Added: packages/python-cs/branches/upstream/python-cs/current/tests.py
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/tests.py	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/tests.py	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,123 @@
+# coding: utf-8
+import os
+
+from contextlib import contextmanager
+from functools import partial
+from unittest import TestCase
+
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
+
+from cs import read_config, CloudStack
+
+
+ at contextmanager
+def env(**kwargs):
+    old_env = {}
+    for key in kwargs:
+        if key in os.environ:
+            old_env[key] = os.environ[key]
+    os.environ.update(kwargs)
+    try:
+        yield
+    finally:
+        for key in kwargs:
+            if key in old_env:
+                os.environ[key] = old_env[key]
+            else:
+                del os.environ[key]
+
+
+ at contextmanager
+def cwd(path):
+    initial = os.getcwd()
+    os.chdir(path)
+    try:
+        yield
+    finally:
+        os.chdir(initial)
+
+
+class ConfigTest(TestCase):
+    def test_env_vars(self):
+        with env(CLOUDSTACK_KEY='test key from env',
+                 CLOUDSTACK_SECRET='test secret from env',
+                 CLOUDSTACK_ENDPOINT='https://api.example.com/from-env'):
+            conf = read_config()
+            self.assertEqual(conf, {
+                'key': 'test key from env',
+                'secret': 'test secret from env',
+                'endpoint': 'https://api.example.com/from-env',
+            })
+
+    def test_current_dir_config(self):
+        with open('/tmp/cloudstack.ini', 'w') as f:
+            f.write('[cloudstack]\n'
+                    'endpoint = https://api.example.com/from-file\n'
+                    'key = test key from file\n'
+                    'secret = test secret from file')
+            self.addCleanup(partial(os.remove, '/tmp/cloudstack.ini'))
+
+        with cwd('/tmp'):
+            conf = read_config()
+            self.assertEqual(dict(conf), {
+                'endpoint': 'https://api.example.com/from-file',
+                'key': 'test key from file',
+                'secret': 'test secret from file',
+            })
+
+
+class RequestTest(TestCase):
+    @patch('requests.get')
+    def test_request_params(self, get):
+        cs = CloudStack(endpoint='localhost', key='foo', secret='bar',
+                        timeout=20)
+        get.return_value.status_code = 200
+        get.return_value.json.return_value = {
+            'listvirtualmachinesresponse': {},
+        }
+        machines = cs.listVirtualMachines(listall='true')
+        self.assertEqual(machines, {})
+        get.assert_called_once_with('localhost', timeout=20, params={
+            'apiKey': 'foo',
+            'response': 'json',
+            'command': 'listVirtualMachines',
+            'listall': 'true',
+            'signature': 'B0d6hBsZTcFVCiioSxzwKA9Pke8='})
+
+    @patch('requests.get')
+    def test_encoding(self, get):
+        cs = CloudStack(endpoint='localhost', key='foo', secret='bar')
+        get.return_value.status_code = 200
+        get.return_value.json.return_value = {
+            'listvirtualmachinesresponse': {},
+        }
+        cs.listVirtualMachines(listall=1, unicode_param=u'éèààû')
+        get.assert_called_once_with('localhost', timeout=10, params={
+            'apiKey': 'foo',
+            'response': 'json',
+            'command': 'listVirtualMachines',
+            'listall': '1',
+            'unicode_param': u'éèààû',
+            'signature': 'gABU/KFJKD3FLAgKDuxQoryu4sA='})
+
+    @patch("requests.get")
+    def test_transformt(self, get):
+        cs = CloudStack(endpoint='localhost', key='foo', secret='bar')
+        get.return_value.status_code = 200
+        get.return_value.json.return_value = {
+            'listvirtualmachinesresponse': {},
+        }
+        cs.listVirtualMachines(foo=["foo", "bar"],
+                               bar=[{'baz': 'blah', 'foo': 'meh'}])
+        get.assert_called_once_with('localhost', timeout=10, params={
+            'command': 'listVirtualMachines',
+            'response': 'json',
+            'bar[0].foo': 'meh',
+            'bar[0].baz': 'blah',
+            'foo': 'foo,bar',
+            'apiKey': 'foo',
+            'signature': 'UGUVEfCOfGfOlqoTj1D2m5adr2g=',
+        })

Added: packages/python-cs/branches/upstream/python-cs/current/tox.ini
===================================================================
--- packages/python-cs/branches/upstream/python-cs/current/tox.ini	                        (rev 0)
+++ packages/python-cs/branches/upstream/python-cs/current/tox.ini	2014-10-30 10:14:54 UTC (rev 31311)
@@ -0,0 +1,24 @@
+[tox]
+envlist =
+	py27,
+	py33,
+	py34,
+	lint
+
+[testenv]
+commands =
+	python setup.py test
+deps =
+	requests
+
+[testenv:py27]
+deps =
+	{[testenv]deps}
+	mock
+
+[testenv:lint]
+deps =
+	flake8
+commands =
+	flake8 cs.py
+	flake8 tests.py




More information about the Python-modules-commits mailing list