<commit_before>"""
Test image pre-processing functions
"""
from nose.tools import assert_true, assert_false

import nibabel
import numpy as np
from numpy.testing import assert_array_equal

from .. import image
from ..._utils import testing

def test_high_variance_confounds():
    # See also test_signals.test_high_variance_confounds()
    # There is only tests on what is added by image.high_variance_confounds()
    # compared to signal.high_variance_confounds()

    shape = (40, 41, 42)
    length = 17
    n_confounds = 10

    img, mask_img = testing.generate_fake_fmri(shape=shape, length=length)

    confounds1 = image.high_variance_confounds(img, mask_img=mask_img,
                                               percentile=10.,
                                               n_confounds=n_confounds)
    assert_true(confounds1.shape == (length, n_confounds))

    # No mask.
    confounds2 = image.high_variance_confounds(img, percentile=10.,
                                               n_confounds=n_confounds)
    assert_true(confounds2.shape == (length, n_confounds))

def test__smooth_array():
    """Test smoothing of images: _smooth_array()"""
    # Impulse in 3D
    data = np.zeros((40, 41, 42))
    data[20, 20, 20] = 1

    # fwhm divided by any test affine must be odd. Otherwise assertion below
    # will fail. ( 9 / 0.6 = 15 is fine)
    fwhm = 9
    test_affines = (np.eye(4), np.diag((1, 1, -1, 1)),
                    np.diag((.6, 1, .6, 1)))
    for affine in test_affines:
        filtered = image._smooth_array(data, affine,
                                         fwhm=fwhm, copy=True)
        assert_false(np.may_share_memory(filtered, data))

        # We are expecting a full-width at half maximum of
        # fwhm / voxel_size:
        vmax = filtered.max()
        above_half_max = filtered > .5 * vmax
        for axis in (0, 1, 2):
            proj = np.any(np.any(np.rollaxis(above_half_max,
                          axis=axis), axis=-1), axis=-1)
            np.testing.assert_equal(proj.sum(),
                                    fwhm / np.abs(affine[axis, axis]))

    # Check that NaNs in the data do not propagate
    data[10, 10, 10] = np.NaN
    filtered = image._smooth_array(data, affine, fwhm=fwhm,
                                   ensure_finite=True, copy=True)
    assert_true(np.all(np.isfinite(filtered)))

    # Check copy=False.
    for affine in test_affines:
        data = np.zeros((40, 41, 42))
        data[20, 20, 20] = 1
        image._smooth_array(data, affine, fwhm=fwhm, copy=False)

        # We are expecting a full-width at half maximum of
        # fwhm / voxel_size:
        vmax = data.max()
        above_half_max = data > .5 * vmax
        for axis in (0, 1, 2):
            proj = np.any(np.any(np.rollaxis(above_half_max,
                          axis=axis), axis=-1), axis=-1)
            np.testing.assert_equal(proj.sum(),
                                    fwhm / np.abs(affine[axis, axis]))

def test_smooth_img():
    # This function only checks added functionalities compared
    # to _smooth_array()
    shapes = ((10, 11, 12), (13, 14, 15))
    lengths = (17, 18)
    fwhm = (1., 2., 3.)

    img1, mask1 = testing.generate_fake_fmri(shape=shapes[0],
                                             length=lengths[0])
    img2, mask2 = testing.generate_fake_fmri(shape=shapes[1],
                                             length=lengths[1])

    for create_files in (False, True):
        with testing.write_tmp_imgs(img1, img2,
                                    create_files=create_files) as imgs:
            # List of images as input
            out = image.smooth_img(imgs, fwhm)
            assert_true(isinstance(out, list))
            assert_true(len(out) == 2)
            for o, s, l in zip(out, shapes, lengths):
                assert_true(o.shape == (s + (l,)))

            # Single image as input
            out = image.smooth_img(imgs[0], fwhm)
            assert_true(isinstance(out, nibabel.Nifti1Image))
            assert_true(out.shape == (shapes[0] + (lengths[0],)))

def test__crop_img_to():
    data = np.zeros((5, 6, 7))
    data[2:4, 1:5, 3:6] = 1
    affine = np.diag((4, 3, 2, 1))
    niimg = nibabel.Nifti1Image(data, affine=affine)

    slices = [slice(2, 4), slice(1, 5), slice(3, 6)]
    cropped_niimg = image._crop_img_to(niimg, slices, copy=False)

    new_origin = np.array((4, 3, 2)) * np.array((2, 1, 3))

    # check that correct part was extracted:
    assert_true((cropped_niimg.get_data() == 1).all())
    assert_true(cropped_niimg.shape == (2, 4, 3))

    # check that affine was adjusted correctly
    assert_true((cropped_niimg.get_affine()[:3, 3] == new_origin).all())

    # check that data was really not copied
    data[2:4, 1:5, 3:6] = 2
    assert_true((cropped_niimg.get_data() == 2).all())

    # check that copying works
    copied_cropped_niimg = image._crop_img_to(niimg, slices)
    data[2:4, 1:5, 3:6] = 1
    assert_true((copied_cropped_niimg.get_data() == 2).all())

def test_crop_img():
    data = np.zeros((5, 6, 7))
    data[2:4, 1:5, 3:6] = 1
    affine = np.diag((4, 3, 2, 1))
    niimg = nibabel.Nifti1Image(data, affine=affine)

    cropped_niimg = image.crop_img(niimg)

    # correction for padding with "-1"
    new_origin = np.array((4, 3, 2)) * np.array((2 - 1, 1 - 1, 3 - 1))

    # check that correct part was extracted:
    # This also corrects for padding
    assert_true((cropped_niimg.get_data()[1:-1, 1:-1, 1:-1] == 1).all())
    assert_true(cropped_niimg.shape == (2 + 2, 4 + 2, 3 + 2))

def test_crop_threshold_tolerance():
    """Check to see whether crop can skip values that are extremely
    close to zero in a relative sense and will crop them away"""

    data = np.zeros([10, 14, 12])
    data[3:7, 3:7, 5:9] = 1.
    active_shape = (4 + 2, 4 + 2, 4 + 2)  # add padding

    # add an infinitesimal outside this block
    data[3, 3, 3] = 1e-12
    affine = np.eye(4)
    niimg = nibabel.Nifti1Image(data, affine=affine)

    cropped_niimg = image.crop_img(niimg)
    assert_true(cropped_niimg.shape == active_shape)

def test_mean_img():
    rng = np.random.RandomState(42)
    data1 = np.zeros((5, 6, 7))
    data2 = rng.rand(5, 6, 7)
    data3 = rng.rand(5, 6, 7, 3)
    affine = np.diag((4, 3, 2, 1))
    niimg1 = nibabel.Nifti1Image(data1, affine=affine)
    niimg2 = nibabel.Nifti1Image(data2, affine=affine)
    niimg3 = nibabel.Nifti1Image(data3, affine=affine)
    for niimgs in ([niimg1, ],
                   [niimg1, niimg2],
                   [niimg2, niimg1, niimg2],
                   [niimg3, niimg1, niimg2], # Mixture of 4D and 3D images
                  ):

        arrays = list()
        # Ground-truth:
        for niimg in niimgs:
            niimg = niimg.get_data()
            if niimg.ndim == 4:
                niimg = np.mean(niimg, axis=-1)
            arrays.append(niimg)
        truth = np.mean(arrays, axis=0)

        mean_img = image.mean_img(niimgs)
        assert_array_equal(mean_img.get_affine(), affine)
        assert_array_equal(mean_img.get_data(), truth)

        # Test with files
        with testing.write_tmp_imgs(*niimgs) as imgs:
            mean_img = image.mean_img(imgs)
            assert_array_equal(mean_img.get_affine(), affine)
            assert_array_equal(mean_img.get_data(), truth)

<commit_msg>TST: test resampling in mean_img<commit_after>"""
Test image pre-processing functions
"""
from nose.tools import assert_true, assert_false

import nibabel
import numpy as np
from numpy.testing import assert_array_equal

from .. import image
from .. import resampling
from ..._utils import testing

def test_high_variance_confounds():
    # See also test_signals.test_high_variance_confounds()
    # There is only tests on what is added by image.high_variance_confounds()
    # compared to signal.high_variance_confounds()

    shape = (40, 41, 42)
    length = 17
    n_confounds = 10

    img, mask_img = testing.generate_fake_fmri(shape=shape, length=length)

    confounds1 = image.high_variance_confounds(img, mask_img=mask_img,
                                               percentile=10.,
                                               n_confounds=n_confounds)
    assert_true(confounds1.shape == (length, n_confounds))

    # No mask.
    confounds2 = image.high_variance_confounds(img, percentile=10.,
                                               n_confounds=n_confounds)
    assert_true(confounds2.shape == (length, n_confounds))

def test__smooth_array():
    """Test smoothing of images: _smooth_array()"""
    # Impulse in 3D
    data = np.zeros((40, 41, 42))
    data[20, 20, 20] = 1

    # fwhm divided by any test affine must be odd. Otherwise assertion below
    # will fail. ( 9 / 0.6 = 15 is fine)
    fwhm = 9
    test_affines = (np.eye(4), np.diag((1, 1, -1, 1)),
                    np.diag((.6, 1, .6, 1)))
    for affine in test_affines:
        filtered = image._smooth_array(data, affine,
                                         fwhm=fwhm, copy=True)
        assert_false(np.may_share_memory(filtered, data))

        # We are expecting a full-width at half maximum of
        # fwhm / voxel_size:
        vmax = filtered.max()
        above_half_max = filtered > .5 * vmax
        for axis in (0, 1, 2):
            proj = np.any(np.any(np.rollaxis(above_half_max,
                          axis=axis), axis=-1), axis=-1)
            np.testing.assert_equal(proj.sum(),
                                    fwhm / np.abs(affine[axis, axis]))

    # Check that NaNs in the data do not propagate
    data[10, 10, 10] = np.NaN
    filtered = image._smooth_array(data, affine, fwhm=fwhm,
                                   ensure_finite=True, copy=True)
    assert_true(np.all(np.isfinite(filtered)))

    # Check copy=False.
    for affine in test_affines:
        data = np.zeros((40, 41, 42))
        data[20, 20, 20] = 1
        image._smooth_array(data, affine, fwhm=fwhm, copy=False)

        # We are expecting a full-width at half maximum of
        # fwhm / voxel_size:
        vmax = data.max()
        above_half_max = data > .5 * vmax
        for axis in (0, 1, 2):
            proj = np.any(np.any(np.rollaxis(above_half_max,
                          axis=axis), axis=-1), axis=-1)
            np.testing.assert_equal(proj.sum(),
                                    fwhm / np.abs(affine[axis, axis]))

def test_smooth_img():
    # This function only checks added functionalities compared
    # to _smooth_array()
    shapes = ((10, 11, 12), (13, 14, 15))
    lengths = (17, 18)
    fwhm = (1., 2., 3.)

    img1, mask1 = testing.generate_fake_fmri(shape=shapes[0],
                                             length=lengths[0])
    img2, mask2 = testing.generate_fake_fmri(shape=shapes[1],
                                             length=lengths[1])

    for create_files in (False, True):
        with testing.write_tmp_imgs(img1, img2,
                                    create_files=create_files) as imgs:
            # List of images as input
            out = image.smooth_img(imgs, fwhm)
            assert_true(isinstance(out, list))
            assert_true(len(out) == 2)
            for o, s, l in zip(out, shapes, lengths):
                assert_true(o.shape == (s + (l,)))

            # Single image as input
            out = image.smooth_img(imgs[0], fwhm)
            assert_true(isinstance(out, nibabel.Nifti1Image))
            assert_true(out.shape == (shapes[0] + (lengths[0],)))

def test__crop_img_to():
    data = np.zeros((5, 6, 7))
    data[2:4, 1:5, 3:6] = 1
    affine = np.diag((4, 3, 2, 1))
    niimg = nibabel.Nifti1Image(data, affine=affine)

    slices = [slice(2, 4), slice(1, 5), slice(3, 6)]
    cropped_niimg = image._crop_img_to(niimg, slices, copy=False)

    new_origin = np.array((4, 3, 2)) * np.array((2, 1, 3))

    # check that correct part was extracted:
    assert_true((cropped_niimg.get_data() == 1).all())
    assert_true(cropped_niimg.shape == (2, 4, 3))

    # check that affine was adjusted correctly
    assert_true((cropped_niimg.get_affine()[:3, 3] == new_origin).all())

    # check that data was really not copied
    data[2:4, 1:5, 3:6] = 2
    assert_true((cropped_niimg.get_data() == 2).all())

    # check that copying works
    copied_cropped_niimg = image._crop_img_to(niimg, slices)
    data[2:4, 1:5, 3:6] = 1
    assert_true((copied_cropped_niimg.get_data() == 2).all())

def test_crop_img():
    data = np.zeros((5, 6, 7))
    data[2:4, 1:5, 3:6] = 1
    affine = np.diag((4, 3, 2, 1))
    niimg = nibabel.Nifti1Image(data, affine=affine)

    cropped_niimg = image.crop_img(niimg)

    # correction for padding with "-1"
    new_origin = np.array((4, 3, 2)) * np.array((2 - 1, 1 - 1, 3 - 1))

    # check that correct part was extracted:
    # This also corrects for padding
    assert_true((cropped_niimg.get_data()[1:-1, 1:-1, 1:-1] == 1).all())
    assert_true(cropped_niimg.shape == (2 + 2, 4 + 2, 3 + 2))

def test_crop_threshold_tolerance():
    """Check to see whether crop can skip values that are extremely
    close to zero in a relative sense and will crop them away"""

    data = np.zeros([10, 14, 12])
    data[3:7, 3:7, 5:9] = 1.
    active_shape = (4 + 2, 4 + 2, 4 + 2)  # add padding

    # add an infinitesimal outside this block
    data[3, 3, 3] = 1e-12
    affine = np.eye(4)
    niimg = nibabel.Nifti1Image(data, affine=affine)

    cropped_niimg = image.crop_img(niimg)
    assert_true(cropped_niimg.shape == active_shape)

def test_mean_img():
    rng = np.random.RandomState(42)
    data1 = np.zeros((5, 6, 7))
    data2 = rng.rand(5, 6, 7)
    data3 = rng.rand(5, 6, 7, 3)
    affine = np.diag((4, 3, 2, 1))
    niimg1 = nibabel.Nifti1Image(data1, affine=affine)
    niimg2 = nibabel.Nifti1Image(data2, affine=affine)
    niimg3 = nibabel.Nifti1Image(data3, affine=affine)
    for niimgs in ([niimg1, ],
                   [niimg1, niimg2],
                   [niimg2, niimg1, niimg2],
                   [niimg3, niimg1, niimg2], # Mixture of 4D and 3D images
                  ):

        arrays = list()
        # Ground-truth:
        for niimg in niimgs:
            niimg = niimg.get_data()
            if niimg.ndim == 4:
                niimg = np.mean(niimg, axis=-1)
            arrays.append(niimg)
        truth = np.mean(arrays, axis=0)

        mean_img = image.mean_img(niimgs)
        assert_array_equal(mean_img.get_affine(), affine)
        assert_array_equal(mean_img.get_data(), truth)

        # Test with files
        with testing.write_tmp_imgs(*niimgs) as imgs:
            mean_img = image.mean_img(imgs)
            assert_array_equal(mean_img.get_affine(), affine)
            assert_array_equal(mean_img.get_data(), truth)

def test_mean_img_resample():
    # Test resampling in mean_img with a permutation of the axes
    rng = np.random.RandomState(42)
    data = rng.rand(5, 6, 7, 40)
    affine = np.diag((4, 3, 2, 1))
    niimg = nibabel.Nifti1Image(data, affine=affine)
    mean_img = nibabel.Nifti1Image(data.mean(axis=-1), affine=affine)

    target_affine = affine[:, [1, 0, 2, 3]]  # permutation of axes
    mean_img_with_resampling = image.mean_img(niimg,
                                              target_affine=target_affine)
    resampled_mean_image = resampling.resample_img(mean_img,
                                              target_affine=target_affine)
    assert_array_equal(resampled_mean_image.get_data(),
                       mean_img_with_resampling.get_data())
    assert_array_equal(resampled_mean_image.get_affine(),
                       mean_img_with_resampling.get_affine())
    assert_array_equal(mean_img_with_resampling.get_affine(), target_affine)

<|endoftext|>


<commit_before>#! python3
# P1_mcb.pyw - Saves and loads pieces of text to the clipboard.
# Usage: py.exe P1_mcb.pyw save <keyword> - Saves clipboard to keyword.
#        py.exe P1_mcb.pyw delete <keyword> - Deletes keyword from database.
#        py.exe P1_mcb.pyw <keyword> - Loads keyword to clipboard.
#        py.exe P1_mcb.pyw list - Loads all keywords to clipboard.
#        py.exe P1_mcb.pyw delete - Deletes all keywords from database.
#
# Extend the multiclipboard program in this chapter so that it has a delete <keyword>
# command line argument that will delete a keyword from the shelf. Then add a delete
# command line argument that will delete all keywords.

import shelve, pyperclip, sys

mcbShelf = shelve.open('mcb')

# Save clipboard content.
if len(sys.argv) == 3 and sys.argv[1].lower() == 'save':
    mcbShelf[sys.argv[2]] = pyperclip.paste()

# Delete clipboard content.
elif len(sys.argv) == 3 and sys.argv[1].lower() == 'delete':
    del mcbShelf[sys.argv[2]]

elif len(sys.argv) == 2:
    # List keywords, delete all keywords, and load content.
    if sys.argv[1].lower() == 'list':
        pyperclip.copy(str(list(mcbShelf.keys())))
    elif sys.argv[1].lower() == 'delete':
        mcbShelf.clear()
    elif sys.argv[1] in mcbShelf:
        pyperclip.copy(mcbShelf[sys.argv[1]])

mcbShelf.close()
<commit_msg>Update P1_mcb.pyw changed pyperclip import to ATBS Ch8<commit_after>#! python3
# P1_mcb.pyw - Saves and loads pieces of text to the clipboard.
# Usage: py.exe P1_mcb.pyw save <keyword> - Saves clipboard to keyword.
#        py.exe P1_mcb.pyw delete <keyword> - Deletes keyword from database.
#        py.exe P1_mcb.pyw <keyword> - Loads keyword to clipboard.
#        py.exe P1_mcb.pyw list - Loads all keywords to clipboard.
#        py.exe P1_mcb.pyw delete - Deletes all keywords from database.
#
# Extend the multiclipboard program in this chapter so that it has a delete <keyword>
# command line argument that will delete a keyword from the shelf. Then add a delete
# command line argument that will delete all keywords.

import shelve, sys
from pythontutorials.books.AutomateTheBoringStuff.Ch08 import pyperclip

mcbShelf = shelve.open('mcb')

# Save clipboard content.
if len(sys.argv) == 3 and sys.argv[1].lower() == 'save':
    mcbShelf[sys.argv[2]] = pyperclip.paste()

# Delete clipboard content.
elif len(sys.argv) == 3 and sys.argv[1].lower() == 'delete':
    del mcbShelf[sys.argv[2]]

elif len(sys.argv) == 2:
    # List keywords, delete all keywords, and load content.
    if sys.argv[1].lower() == 'list':
        pyperclip.copy(str(list(mcbShelf.keys())))
    elif sys.argv[1].lower() == 'delete':
        mcbShelf.clear()
    elif sys.argv[1] in mcbShelf:
        pyperclip.copy(mcbShelf[sys.argv[1]])

mcbShelf.close()
<|endoftext|>


<commit_before># coding: utf-8

from qingcloud.cli.iaas_client.actions.base import BaseAction

class DescribeSecurityGroupRulesAction(BaseAction):

    action = 'DescribeSecurityGroupRules'
    command = 'describe-security-group-rules'
    usage = '%(prog)s -s <security_group_id> -r <security_group_rules> [-f <conf_file>]'

    @classmethod
    def add_ext_arguments(cls, parser):
        parser.add_argument('-s', '--security_group', dest='security_group',
                action='store', type=str, default='',
                help='ID of security_group whose rules you want to list. ')

        parser.add_argument('-r', '--security_group_rules', dest='security_group_rules',
                action='store', type=str, default='',
                help='ID of security group rule you want to list. ')

        parser.add_argument('-d', '--direction', dest='direction',
                action='store', type=int, default=None,
                help='0 for inbound; 1 for outbound.')

    @classmethod
    def build_directive(cls, options):
        return {
                'security_group': options.security_group,
                'security_group_rules': options.security_group_rules,
                'direction': options.direction,
                'offset':options.offset,
                'limit': options.limit,
                }
<commit_msg>Bugfix: describe security group rules<commit_after># coding: utf-8

from qingcloud.cli.misc.utils import explode_array
from qingcloud.cli.iaas_client.actions.base import BaseAction

class DescribeSecurityGroupRulesAction(BaseAction):

    action = 'DescribeSecurityGroupRules'
    command = 'describe-security-group-rules'
    usage = '%(prog)s -s <security_group_id> -r <security_group_rules> [-f <conf_file>]'

    @classmethod
    def add_ext_arguments(cls, parser):
        parser.add_argument('-s', '--security_group', dest='security_group',
                action='store', type=str, default='',
                help='ID of security_group whose rules you want to list. ')

        parser.add_argument('-r', '--security_group_rules', dest='security_group_rules',
                action='store', type=str, default='',
                help='ID of security group rule you want to list. ')

        parser.add_argument('-d', '--direction', dest='direction',
                action='store', type=int, default=None,
                help='0 for inbound; 1 for outbound.')

    @classmethod
    def build_directive(cls, options):
        return {
                'security_group': options.security_group,
                'security_group_rules': explode_array(options.security_group_rules),
                'direction': options.direction,
                'offset':options.offset,
                'limit': options.limit,
                }
<|endoftext|>


<commit_before>#!/usr/bin/env python3

import subprocess
import asyncio

from .common import Cog
from discord.ext import commands

def pip_freeze():
    out = subprocess.check_output('pip freeze', shell=True)
    return out

class PipUpdates(Cog):
    def __init__(self, bot):
        super().__init__(bot)

        self.watch = {}
        self.requirements = {}

        reqlist = None
        with open('requirements.txt', 'r') as reqfile:
            reqlist = (reqfile.read()).split('\n')

        for pkg in reqlist:
            r = pkg.split('==')
            if len(r) != 2:
                continue
            pkgname, pkgversion = r[0], r[1]
            self.requirements[pkgname] = pkgversion

        self.update_task = self.bot.loop.create_task(self.update_task_func())

    def __unload(self):
        self.update_task.cancel()

    async def update_task_func(self):
        try:
            while True:
                await self.checkupdates()
                await asyncio.sleep(21600)
        except asyncio.CancelledError:
            pass

    async def checkupdates(self):
        future_pip = self.bot.loop.run_in_executor(None, pip_freeze)
        out = await future_pip
        out = out.decode('utf-8')
        packages = out.split('\n')

        res = []

        for pkgline in packages:
            r = pkgline.split('==')
            if len(r) != 2:
                continue
            pkgname = r[0]

            if pkgname == 'discord.py[voice]':
                pḱgname = 'discord.py'

            if pkgname in self.requirements:
                cur_version = self.requirements[pkgname]

                pkgdata = await self.get_json(f'http://pypi.python.org/pypi/{pkgname}/json')

                new_version = pkgdata['info']['version']
                log.info('[pip] checking current=%s against latest=%s', \
                         cur_version, new_version)

                if new_version != cur_version:
                    log.info('[pip] New update for %s', pkgname)
                    res.append(" * `%r` needs update from %s to %s" % \
                        (pkgname, cur_version, new_version))

        await self.say_results(res)
        return res

    async def say_results(self, res):
        if len(res) <= 0:
            return

        owner = (await self.bot.application_info()).owner
        res.insert(0, ':alarm_clock: You have package updates :alarm_clock:')
        await owner.send('\n'.join(res))

    @commands.command(hidden=True)
    @commands.is_owner()
    async def checkpkgs(self, ctx):
        """Query PyPI for new package updates."""
        async with ctx.typing():
            res = await self.checkupdates()

            if len(res) <= 0:
                return await ctx.send("`No updates found.`")

            await ctx.send('Updates were found and should be sent to the bot owner!')

def setup(bot):
    bot.add_cog(PipUpdates(bot))
<commit_msg>pipupdates: hotfix<commit_after>#!/usr/bin/env python3

import subprocess
import asyncio
import logging

from .common import Cog
from discord.ext import commands

log = logging.getLogger(__name__)

def pip_freeze():
    out = subprocess.check_output('pip freeze', shell=True)
    return out

class PipUpdates(Cog):
    def __init__(self, bot):
        super().__init__(bot)

        self.watch = {}
        self.requirements = {}

        reqlist = None
        with open('requirements.txt', 'r') as reqfile:
            reqlist = (reqfile.read()).split('\n')

        for pkg in reqlist:
            r = pkg.split('==')
            if len(r) != 2:
                continue
            pkgname, pkgversion = r[0], r[1]
            self.requirements[pkgname] = pkgversion

        self.update_task = self.bot.loop.create_task(self.update_task_func())

    def __unload(self):
        self.update_task.cancel()

    async def update_task_func(self):
        try:
            while True:
                await self.checkupdates()
                await asyncio.sleep(21600)
        except asyncio.CancelledError:
            pass

    async def checkupdates(self):
        future_pip = self.bot.loop.run_in_executor(None, pip_freeze)
        out = await future_pip
        out = out.decode('utf-8')
        packages = out.split('\n')

        res = []

        for pkgline in packages:
            r = pkgline.split('==')
            if len(r) != 2:
                continue
            pkgname = r[0]

            if pkgname == 'discord.py[voice]':
                pḱgname = 'discord.py'

            if pkgname in self.requirements:
                cur_version = self.requirements[pkgname]

                pkgdata = await self.get_json(f'http://pypi.python.org/pypi/{pkgname}/json')

                new_version = pkgdata['info']['version']
                log.info('[pip] checking current=%s against latest=%s', \
                         cur_version, new_version)

                if new_version != cur_version:
                    log.info('[pip] New update for %s', pkgname)
                    res.append(" * `%r` needs update from %s to %s" % \
                        (pkgname, cur_version, new_version))

        await self.say_results(res)
        return res

    async def say_results(self, res):
        if len(res) <= 0:
            return

        owner = (await self.bot.application_info()).owner
        res.insert(0, ':alarm_clock: You have package updates :alarm_clock:')
        await owner.send('\n'.join(res))

    @commands.command(hidden=True)
    @commands.is_owner()
    async def checkpkgs(self, ctx):
        """Query PyPI for new package updates."""
        async with ctx.typing():
            res = await self.checkupdates()

            if len(res) <= 0:
                return await ctx.send("`No updates found.`")

            await ctx.send('Updates were found and should be sent to the bot owner!')

def setup(bot):
    bot.add_cog(PipUpdates(bot))
<|endoftext|>


<commit_before># Copyright (c) 2011 OpenStack Foundation
# 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. 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.

"""
The FilterScheduler is for creating instances locally.
You can customize this scheduler by specifying your own Host Filters and
Weighing Functions.
"""

import random

from oslo_config import cfg
from oslo_log import log as logging

from nova import exception
from nova.i18n import _
from nova import rpc
from nova.scheduler import driver
from nova.scheduler import scheduler_options

CONF = cfg.CONF
LOG = logging.getLogger(__name__)

filter_scheduler_opts = [
    cfg.IntOpt('scheduler_host_subset_size',
               default=1,
               help='New instances will be scheduled on a host chosen '
                    'randomly from a subset of the N best hosts. This '
                    'property defines the subset size that a host is '
                    'chosen from. A value of 1 chooses the '
                    'first host returned by the weighing functions. '
                    'This value must be at least 1. Any value less than 1 '
                    'will be ignored, and 1 will be used instead')
]

CONF.register_opts(filter_scheduler_opts)

class FilterScheduler(driver.Scheduler):
    """Scheduler that can be used for filtering and weighing."""
    def __init__(self, *args, **kwargs):
        super(FilterScheduler, self).__init__(*args, **kwargs)
        self.options = scheduler_options.SchedulerOptions()
        self.notifier = rpc.get_notifier('scheduler')

    def select_destinations(self, context, request_spec, filter_properties):
        """Selects a filtered set of hosts and nodes."""
        self.notifier.info(context, 'scheduler.select_destinations.start',
                           dict(request_spec=request_spec))

        num_instances = request_spec['num_instances']
        selected_hosts = self._schedule(context, request_spec,
                                        filter_properties)

        # Couldn't fulfill the request_spec
        if len(selected_hosts) < num_instances:
            # Log the details but don't put those into the reason since
            # we don't want to give away too much information about our
            # actual environment.
            LOG.debug('There are %(hosts)d hosts available but '
                      '%(num_instances)d instances requested to build.',
                      {'hosts': len(selected_hosts),
                       'num_instances': num_instances})

            reason = _('There are not enough hosts available.')
            raise exception.NoValidHost(reason=reason)

        dests = [dict(host=host.obj.host, nodename=host.obj.nodename,
                      limits=host.obj.limits) for host in selected_hosts]

        self.notifier.info(context, 'scheduler.select_destinations.end',
                           dict(request_spec=request_spec))
        return dests

    def _get_configuration_options(self):
        """Fetch options dictionary. Broken out for testing."""
        return self.options.get_configuration()

    def populate_filter_properties(self, request_spec, filter_properties):
        """Stuff things into filter_properties.  Can be overridden in a
        subclass to add more data.
        """
        # Save useful information from the request spec for filter processing:
        project_id = request_spec['instance_properties']['project_id']
        os_type = request_spec['instance_properties']['os_type']
        filter_properties['project_id'] = project_id
        filter_properties['os_type'] = os_type

    def _schedule(self, context, request_spec, filter_properties):
        """Returns a list of hosts that meet the required specs,
        ordered by their fitness.
        """
        elevated = context.elevated()
        instance_properties = request_spec['instance_properties']
        instance_type = request_spec.get("instance_type", None)

        update_group_hosts = filter_properties.get('group_updated', False)

        config_options = self._get_configuration_options()

        filter_properties.update({'context': context,
                                  'request_spec': request_spec,
                                  'config_options': config_options,
                                  'instance_type': instance_type})

        self.populate_filter_properties(request_spec,
                                        filter_properties)

        # Find our local list of acceptable hosts by repeatedly
        # filtering and weighing our options. Each time we choose a
        # host, we virtually consume resources on it so subsequent
        # selections can adjust accordingly.

        # Note: remember, we are using an iterator here. So only
        # traverse this list once. This can bite you if the hosts
        # are being scanned in a filter or weighing function.
        hosts = self._get_all_host_states(elevated)

        selected_hosts = []
        num_instances = request_spec.get('num_instances', 1)
        for num in xrange(num_instances):
            # Filter local hosts based on requirements ...
            hosts = self.host_manager.get_filtered_hosts(hosts,
                    filter_properties, index=num)
            if not hosts:
                # Can't get any more locally.
                break

            LOG.debug("Filtered %(hosts)s", {'hosts': hosts})

            weighed_hosts = self.host_manager.get_weighed_hosts(hosts,
                    filter_properties)

            LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts})

            scheduler_host_subset_size = CONF.scheduler_host_subset_size
            if scheduler_host_subset_size > len(weighed_hosts):
                scheduler_host_subset_size = len(weighed_hosts)
            if scheduler_host_subset_size < 1:
                scheduler_host_subset_size = 1

            chosen_host = random.choice(
                weighed_hosts[0:scheduler_host_subset_size])
            selected_hosts.append(chosen_host)

            # Now consume the resources so the filter/weights
            # will change for the next instance.
            chosen_host.obj.consume_from_instance(instance_properties)
            if update_group_hosts is True:
                # NOTE(sbauza): Group details are serialized into a list now
                # that they are populated by the conductor, we need to
                # deserialize them
                if isinstance(filter_properties['group_hosts'], list):
                    filter_properties['group_hosts'] = set(
                        filter_properties['group_hosts'])
                filter_properties['group_hosts'].add(chosen_host.obj.host)
        return selected_hosts

    def _get_all_host_states(self, context):
        """Template method, so a subclass can implement caching."""
        return self.host_manager.get_all_host_states(context)
<commit_msg>Display host chosen for instance by scheduler<commit_after># Copyright (c) 2011 OpenStack Foundation
# 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. 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.

"""
The FilterScheduler is for creating instances locally.
You can customize this scheduler by specifying your own Host Filters and
Weighing Functions.
"""

import random

from oslo_config import cfg
from oslo_log import log as logging

from nova import exception
from nova.i18n import _
from nova import rpc
from nova.scheduler import driver
from nova.scheduler import scheduler_options

CONF = cfg.CONF
LOG = logging.getLogger(__name__)

filter_scheduler_opts = [
    cfg.IntOpt('scheduler_host_subset_size',
               default=1,
               help='New instances will be scheduled on a host chosen '
                    'randomly from a subset of the N best hosts. This '
                    'property defines the subset size that a host is '
                    'chosen from. A value of 1 chooses the '
                    'first host returned by the weighing functions. '
                    'This value must be at least 1. Any value less than 1 '
                    'will be ignored, and 1 will be used instead')
]

CONF.register_opts(filter_scheduler_opts)

class FilterScheduler(driver.Scheduler):
    """Scheduler that can be used for filtering and weighing."""
    def __init__(self, *args, **kwargs):
        super(FilterScheduler, self).__init__(*args, **kwargs)
        self.options = scheduler_options.SchedulerOptions()
        self.notifier = rpc.get_notifier('scheduler')

    def select_destinations(self, context, request_spec, filter_properties):
        """Selects a filtered set of hosts and nodes."""
        self.notifier.info(context, 'scheduler.select_destinations.start',
                           dict(request_spec=request_spec))

        num_instances = request_spec['num_instances']
        selected_hosts = self._schedule(context, request_spec,
                                        filter_properties)

        # Couldn't fulfill the request_spec
        if len(selected_hosts) < num_instances:
            # Log the details but don't put those into the reason since
            # we don't want to give away too much information about our
            # actual environment.
            LOG.debug('There are %(hosts)d hosts available but '
                      '%(num_instances)d instances requested to build.',
                      {'hosts': len(selected_hosts),
                       'num_instances': num_instances})

            reason = _('There are not enough hosts available.')
            raise exception.NoValidHost(reason=reason)

        dests = [dict(host=host.obj.host, nodename=host.obj.nodename,
                      limits=host.obj.limits) for host in selected_hosts]

        self.notifier.info(context, 'scheduler.select_destinations.end',
                           dict(request_spec=request_spec))
        return dests

    def _get_configuration_options(self):
        """Fetch options dictionary. Broken out for testing."""
        return self.options.get_configuration()

    def populate_filter_properties(self, request_spec, filter_properties):
        """Stuff things into filter_properties.  Can be overridden in a
        subclass to add more data.
        """
        # Save useful information from the request spec for filter processing:
        project_id = request_spec['instance_properties']['project_id']
        os_type = request_spec['instance_properties']['os_type']
        filter_properties['project_id'] = project_id
        filter_properties['os_type'] = os_type

    def _schedule(self, context, request_spec, filter_properties):
        """Returns a list of hosts that meet the required specs,
        ordered by their fitness.
        """
        elevated = context.elevated()
        instance_properties = request_spec['instance_properties']
        instance_type = request_spec.get("instance_type", None)

        update_group_hosts = filter_properties.get('group_updated', False)

        config_options = self._get_configuration_options()

        filter_properties.update({'context': context,
                                  'request_spec': request_spec,
                                  'config_options': config_options,
                                  'instance_type': instance_type})

        self.populate_filter_properties(request_spec,
                                        filter_properties)

        # Find our local list of acceptable hosts by repeatedly
        # filtering and weighing our options. Each time we choose a
        # host, we virtually consume resources on it so subsequent
        # selections can adjust accordingly.

        # Note: remember, we are using an iterator here. So only
        # traverse this list once. This can bite you if the hosts
        # are being scanned in a filter or weighing function.
        hosts = self._get_all_host_states(elevated)

        selected_hosts = []
        num_instances = request_spec.get('num_instances', 1)
        for num in xrange(num_instances):
            # Filter local hosts based on requirements ...
            hosts = self.host_manager.get_filtered_hosts(hosts,
                    filter_properties, index=num)
            if not hosts:
                # Can't get any more locally.
                break

            LOG.debug("Filtered %(hosts)s", {'hosts': hosts})

            weighed_hosts = self.host_manager.get_weighed_hosts(hosts,
                    filter_properties)

            LOG.debug("Weighed %(hosts)s", {'hosts': weighed_hosts})

            scheduler_host_subset_size = CONF.scheduler_host_subset_size
            if scheduler_host_subset_size > len(weighed_hosts):
                scheduler_host_subset_size = len(weighed_hosts)
            if scheduler_host_subset_size < 1:
                scheduler_host_subset_size = 1

            chosen_host = random.choice(
                weighed_hosts[0:scheduler_host_subset_size])
            LOG.debug("Selected host: %(host)s", {'host': chosen_host})
            selected_hosts.append(chosen_host)

            # Now consume the resources so the filter/weights
            # will change for the next instance.
            chosen_host.obj.consume_from_instance(instance_properties)
            if update_group_hosts is True:
                # NOTE(sbauza): Group details are serialized into a list now
                # that they are populated by the conductor, we need to
                # deserialize them
                if isinstance(filter_properties['group_hosts'], list):
                    filter_properties['group_hosts'] = set(
                        filter_properties['group_hosts'])
                filter_properties['group_hosts'].add(chosen_host.obj.host)
        return selected_hosts

    def _get_all_host_states(self, context):
        """Template method, so a subclass can implement caching."""
        return self.host_manager.get_all_host_states(context)
<|endoftext|>


<commit_before># Copyright 2013 OpenStack Foundation
#
#    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.

import contextlib

try:
    import cPickle as pickle
except ImportError:
    import pickle

import errno
import socket
import time

from eventlet import queue
from eventlet import timeout
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import versionutils
from six.moves import range

try:
    import xmlrpclib
except ImportError:
    import six.moves.xmlrpc_client as xmlrpclib

from nova import context
from nova import exception
from nova.i18n import _, _LE, _LW
from nova import objects
from nova import utils
from nova import version
from nova.virt.xenapi.client import objects as cli_objects
from nova.virt.xenapi import pool
from nova.virt.xenapi import pool_states

LOG = logging.getLogger(__name__)

xenapi_session_opts = [
    cfg.IntOpt('login_timeout',
               default=10,
               help='Timeout in seconds for XenAPI login.'),
    cfg.IntOpt('connection_concurrent',
               default=5,
               help='Maximum number of concurrent XenAPI connections. '
                    'Used only if compute_driver=xenapi.XenAPIDriver'),
    ]

CONF = cfg.CONF
CONF.register_opts(xenapi_session_opts, 'xenserver')
CONF.import_opt('host', 'nova.netconf')

def apply_session_helpers(session):
    session.VM = cli_objects.VM(session)
    session.SR = cli_objects.SR(session)
    session.VDI = cli_objects.VDI(session)
    session.VBD = cli_objects.VBD(session)
    session.PBD = cli_objects.PBD(session)
    session.PIF = cli_objects.PIF(session)
    session.VLAN = cli_objects.VLAN(session)
    session.host = cli_objects.Host(session)
    session.network = cli_objects.Network(session)
    session.pool = cli_objects.Pool(session)

class XenAPISession(object):
    """The session to invoke XenAPI SDK calls."""

    # This is not a config option as it should only ever be
    # changed in development environments.
    # MAJOR VERSION: Incompatible changes with the plugins
    # MINOR VERSION: Compatible changes, new plguins, etc
    PLUGIN_REQUIRED_VERSION = '1.2'

    def __init__(self, url, user, pw):
        version_string = version.version_string_with_package()
        self.nova_version = _('%(vendor)s %(product)s %(version)s') % \
                              {'vendor': version.vendor_string(),
                               'product': version.product_string(),
                               'version': version_string}
        import XenAPI
        self.XenAPI = XenAPI
        self._sessions = queue.Queue()
        self.is_slave = False
        exception = self.XenAPI.Failure(_("Unable to log in to XenAPI "
                                          "(is the Dom0 disk full?)"))
        url = self._create_first_session(url, user, pw, exception)
        self._populate_session_pool(url, user, pw, exception)
        self.host_uuid = self._get_host_uuid()
        self.host_ref = self._get_host_ref()
        self.product_version, self.product_brand = \
            self._get_product_version_and_brand()

        self._verify_plugin_version()

        apply_session_helpers(self)

    def _verify_plugin_version(self):
        requested_version = self.PLUGIN_REQUIRED_VERSION
        current_version = self.call_plugin_serialized(
            'nova_plugin_version', 'get_version')

        if not versionutils.is_compatible(requested_version, current_version):
            raise self.XenAPI.Failure(
                _("Plugin version mismatch (Expected %(exp)s, got %(got)s)") %
                {'exp': requested_version, 'got': current_version})

    def _create_first_session(self, url, user, pw, exception):
        try:
            session = self._create_session(url)
            with timeout.Timeout(CONF.xenserver.login_timeout, exception):
                session.login_with_password(user, pw,
                                            self.nova_version, 'OpenStack')
        except self.XenAPI.Failure as e:
            # if user and pw of the master are different, we're doomed!
            if e.details[0] == 'HOST_IS_SLAVE':
                master = e.details[1]
                url = pool.swap_xapi_host(url, master)
                session = self.XenAPI.Session(url)
                session.login_with_password(user, pw,
                                            self.nova_version, 'OpenStack')
                self.is_slave = True
            else:
                raise
        self._sessions.put(session)
        return url

    def _populate_session_pool(self, url, user, pw, exception):
        for i in range(CONF.xenserver.connection_concurrent - 1):
            session = self._create_session(url)
            with timeout.Timeout(CONF.xenserver.login_timeout, exception):
                session.login_with_password(user, pw,
                                            self.nova_version, 'OpenStack')
            self._sessions.put(session)

    def _get_host_uuid(self):
        if self.is_slave:
            aggr = objects.AggregateList.get_by_host(
                context.get_admin_context(),
                CONF.host, key=pool_states.POOL_FLAG)[0]
            if not aggr:
                LOG.error(_LE('Host is member of a pool, but DB '
                              'says otherwise'))
                raise exception.AggregateHostNotFound()
            return aggr.metadetails[CONF.host]
        else:
            with self._get_session() as session:
                host_ref = session.xenapi.session.get_this_host(session.handle)
                return session.xenapi.host.get_uuid(host_ref)

    def _get_product_version_and_brand(self):
        """Return a tuple of (major, minor, rev) for the host version and
        a string of the product brand.
        """
        software_version = self._get_software_version()

        product_version_str = software_version.get('product_version')
        # Product version is only set in some cases (e.g. XCP, XenServer) and
        # not in others (e.g. xenserver-core, XAPI-XCP).
        # In these cases, the platform version is the best number to use.
        if product_version_str is None:
            product_version_str = software_version.get('platform_version',
                                                       '0.0.0')
        product_brand = software_version.get('product_brand')
        product_version = utils.convert_version_to_tuple(product_version_str)

        return product_version, product_brand

    def _get_software_version(self):
        return self.call_xenapi('host.get_software_version', self.host_ref)

    def get_session_id(self):
        """Return a string session_id.  Used for vnc consoles."""
        with self._get_session() as session:
            return str(session._session)

    @contextlib.contextmanager
    def _get_session(self):
        """Return exclusive session for scope of with statement."""
        session = self._sessions.get()
        try:
            yield session
        finally:
            self._sessions.put(session)

    def _get_host_ref(self):
        """Return the xenapi host on which nova-compute runs on."""
        with self._get_session() as session:
            return session.xenapi.host.get_by_uuid(self.host_uuid)

    def call_xenapi(self, method, *args):
        """Call the specified XenAPI method on a background thread."""
        with self._get_session() as session:
            return session.xenapi_request(method, args)

    def call_plugin(self, plugin, fn, args):
        """Call host.call_plugin on a background thread."""
        # NOTE(armando): pass the host uuid along with the args so that
        # the plugin gets executed on the right host when using XS pools
        args['host_uuid'] = self.host_uuid

        with self._get_session() as session:
            return self._unwrap_plugin_exceptions(
                                 session.xenapi.host.call_plugin,
                                 self.host_ref, plugin, fn, args)

    def call_plugin_serialized(self, plugin, fn, *args, **kwargs):
        params = {'params': pickle.dumps(dict(args=args, kwargs=kwargs))}
        rv = self.call_plugin(plugin, fn, params)
        return pickle.loads(rv)

    def call_plugin_serialized_with_retry(self, plugin, fn, num_retries,
                                          callback, retry_cb=None, *args,
                                          **kwargs):
        """Allows a plugin to raise RetryableError so we can try again."""
        attempts = num_retries + 1
        sleep_time = 0.5
        for attempt in range(1, attempts + 1):
            try:
                if attempt > 1:
                    time.sleep(sleep_time)
                    sleep_time = min(2 * sleep_time, 15)

                callback_result = None
                if callback:
                    callback_result = callback(kwargs)

                msg = ('%(plugin)s.%(fn)s attempt %(attempt)d/%(attempts)d, '
                       'callback_result: %(callback_result)s')
                LOG.debug(msg,
                          {'plugin': plugin, 'fn': fn, 'attempt': attempt,
                           'attempts': attempts,
                           'callback_result': callback_result})
                return self.call_plugin_serialized(plugin, fn, *args, **kwargs)
            except self.XenAPI.Failure as exc:
                if self._is_retryable_exception(exc, fn):
                    LOG.warning(_LW('%(plugin)s.%(fn)s failed. '
                                    'Retrying call.'),
                                {'plugin': plugin, 'fn': fn})
                    if retry_cb:
                        retry_cb(exc=exc)
                else:
                    raise
            except socket.error as exc:
                if exc.errno == errno.ECONNRESET:
                    LOG.warning(_LW('Lost connection to XenAPI during call to '
                                    '%(plugin)s.%(fn)s.  Retrying call.'),
                                {'plugin': plugin, 'fn': fn})
                    if retry_cb:
                        retry_cb(exc=exc)
                else:
                    raise

        raise exception.PluginRetriesExceeded(num_retries=num_retries)

    def _is_retryable_exception(self, exc, fn):
        _type, method, error = exc.details[:3]
        if error == 'RetryableError':
            LOG.debug("RetryableError, so retrying %(fn)s", {'fn': fn},
                      exc_info=True)
            return True
        elif "signal" in method:
            LOG.debug("Error due to a signal, retrying %(fn)s", {'fn': fn},
                      exc_info=True)
            return True
        else:
            return False

    def _create_session(self, url):
        """Stubout point. This can be replaced with a mock session."""
        self.is_local_connection = url == "unix://local"
        if self.is_local_connection:
            return self.XenAPI.xapi_local()
        return self.XenAPI.Session(url)

    def _unwrap_plugin_exceptions(self, func, *args, **kwargs):
        """Parse exception details."""
        try:
            return func(*args, **kwargs)
        except self.XenAPI.Failure as exc:
            LOG.debug("Got exception: %s", exc)
            if (len(exc.details) == 4 and
                exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and
                    exc.details[2] == 'Failure'):
                params = None
                try:
                    # FIXME(comstud): eval is evil.
                    params = eval(exc.details[3])
                except Exception:
                    raise exc
                raise self.XenAPI.Failure(params)
            else:
                raise
        except xmlrpclib.ProtocolError as exc:
            LOG.debug("Got exception: %s", exc)
            raise

    def get_rec(self, record_type, ref):
        try:
            return self.call_xenapi('%s.get_record' % record_type, ref)
        except self.XenAPI.Failure as e:
            if e.details[0] != 'HANDLE_INVALID':
                raise

        return None

    def get_all_refs_and_recs(self, record_type):
        """Retrieve all refs and recs for a Xen record type.

        Handles race-conditions where the record may be deleted between
        the `get_all` call and the `get_record` call.
        """

        return self.call_xenapi('%s.get_all_records' % record_type).items()
<commit_msg>xenapi: add necessary timeout check<commit_after># Copyright 2013 OpenStack Foundation
#
#    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.

import contextlib

try:
    import cPickle as pickle
except ImportError:
    import pickle

import errno
import socket
import time

from eventlet import queue
from eventlet import timeout
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import versionutils
from six.moves import range

try:
    import xmlrpclib
except ImportError:
    import six.moves.xmlrpc_client as xmlrpclib

from nova import context
from nova import exception
from nova.i18n import _, _LE, _LW
from nova import objects
from nova import utils
from nova import version
from nova.virt.xenapi.client import objects as cli_objects
from nova.virt.xenapi import pool
from nova.virt.xenapi import pool_states

LOG = logging.getLogger(__name__)

xenapi_session_opts = [
    cfg.IntOpt('login_timeout',
               default=10,
               help='Timeout in seconds for XenAPI login.'),
    cfg.IntOpt('connection_concurrent',
               default=5,
               help='Maximum number of concurrent XenAPI connections. '
                    'Used only if compute_driver=xenapi.XenAPIDriver'),
    ]

CONF = cfg.CONF
CONF.register_opts(xenapi_session_opts, 'xenserver')
CONF.import_opt('host', 'nova.netconf')

def apply_session_helpers(session):
    session.VM = cli_objects.VM(session)
    session.SR = cli_objects.SR(session)
    session.VDI = cli_objects.VDI(session)
    session.VBD = cli_objects.VBD(session)
    session.PBD = cli_objects.PBD(session)
    session.PIF = cli_objects.PIF(session)
    session.VLAN = cli_objects.VLAN(session)
    session.host = cli_objects.Host(session)
    session.network = cli_objects.Network(session)
    session.pool = cli_objects.Pool(session)

class XenAPISession(object):
    """The session to invoke XenAPI SDK calls."""

    # This is not a config option as it should only ever be
    # changed in development environments.
    # MAJOR VERSION: Incompatible changes with the plugins
    # MINOR VERSION: Compatible changes, new plguins, etc
    PLUGIN_REQUIRED_VERSION = '1.2'

    def __init__(self, url, user, pw):
        version_string = version.version_string_with_package()
        self.nova_version = _('%(vendor)s %(product)s %(version)s') % \
                              {'vendor': version.vendor_string(),
                               'product': version.product_string(),
                               'version': version_string}
        import XenAPI
        self.XenAPI = XenAPI
        self._sessions = queue.Queue()
        self.is_slave = False
        exception = self.XenAPI.Failure(_("Unable to log in to XenAPI "
                                          "(is the Dom0 disk full?)"))
        url = self._create_first_session(url, user, pw, exception)
        self._populate_session_pool(url, user, pw, exception)
        self.host_uuid = self._get_host_uuid()
        self.host_ref = self._get_host_ref()
        self.product_version, self.product_brand = \
            self._get_product_version_and_brand()

        self._verify_plugin_version()

        apply_session_helpers(self)

    def _verify_plugin_version(self):
        requested_version = self.PLUGIN_REQUIRED_VERSION
        current_version = self.call_plugin_serialized(
            'nova_plugin_version', 'get_version')

        if not versionutils.is_compatible(requested_version, current_version):
            raise self.XenAPI.Failure(
                _("Plugin version mismatch (Expected %(exp)s, got %(got)s)") %
                {'exp': requested_version, 'got': current_version})

    def _create_first_session(self, url, user, pw, exception):
        try:
            session = self._create_session(url)
            with timeout.Timeout(CONF.xenserver.login_timeout, exception):
                session.login_with_password(user, pw,
                                            self.nova_version, 'OpenStack')
        except self.XenAPI.Failure as e:
            # if user and pw of the master are different, we're doomed!
            if e.details[0] == 'HOST_IS_SLAVE':
                master = e.details[1]
                url = pool.swap_xapi_host(url, master)
                session = self.XenAPI.Session(url)
                with timeout.Timeout(CONF.xenserver.login_timeout, exception):
                    session.login_with_password(user, pw,
                                                self.nova_version, 'OpenStack')
                self.is_slave = True
            else:
                raise
        self._sessions.put(session)
        return url

    def _populate_session_pool(self, url, user, pw, exception):
        for i in range(CONF.xenserver.connection_concurrent - 1):
            session = self._create_session(url)
            with timeout.Timeout(CONF.xenserver.login_timeout, exception):
                session.login_with_password(user, pw,
                                            self.nova_version, 'OpenStack')
            self._sessions.put(session)

    def _get_host_uuid(self):
        if self.is_slave:
            aggr = objects.AggregateList.get_by_host(
                context.get_admin_context(),
                CONF.host, key=pool_states.POOL_FLAG)[0]
            if not aggr:
                LOG.error(_LE('Host is member of a pool, but DB '
                              'says otherwise'))
                raise exception.AggregateHostNotFound()
            return aggr.metadetails[CONF.host]
        else:
            with self._get_session() as session:
                host_ref = session.xenapi.session.get_this_host(session.handle)
                return session.xenapi.host.get_uuid(host_ref)

    def _get_product_version_and_brand(self):
        """Return a tuple of (major, minor, rev) for the host version and
        a string of the product brand.
        """
        software_version = self._get_software_version()

        product_version_str = software_version.get('product_version')
        # Product version is only set in some cases (e.g. XCP, XenServer) and
        # not in others (e.g. xenserver-core, XAPI-XCP).
        # In these cases, the platform version is the best number to use.
        if product_version_str is None:
            product_version_str = software_version.get('platform_version',
                                                       '0.0.0')
        product_brand = software_version.get('product_brand')
        product_version = utils.convert_version_to_tuple(product_version_str)

        return product_version, product_brand

    def _get_software_version(self):
        return self.call_xenapi('host.get_software_version', self.host_ref)

    def get_session_id(self):
        """Return a string session_id.  Used for vnc consoles."""
        with self._get_session() as session:
            return str(session._session)

    @contextlib.contextmanager
    def _get_session(self):
        """Return exclusive session for scope of with statement."""
        session = self._sessions.get()
        try:
            yield session
        finally:
            self._sessions.put(session)

    def _get_host_ref(self):
        """Return the xenapi host on which nova-compute runs on."""
        with self._get_session() as session:
            return session.xenapi.host.get_by_uuid(self.host_uuid)

    def call_xenapi(self, method, *args):
        """Call the specified XenAPI method on a background thread."""
        with self._get_session() as session:
            return session.xenapi_request(method, args)

    def call_plugin(self, plugin, fn, args):
        """Call host.call_plugin on a background thread."""
        # NOTE(armando): pass the host uuid along with the args so that
        # the plugin gets executed on the right host when using XS pools
        args['host_uuid'] = self.host_uuid

        with self._get_session() as session:
            return self._unwrap_plugin_exceptions(
                                 session.xenapi.host.call_plugin,
                                 self.host_ref, plugin, fn, args)

    def call_plugin_serialized(self, plugin, fn, *args, **kwargs):
        params = {'params': pickle.dumps(dict(args=args, kwargs=kwargs))}
        rv = self.call_plugin(plugin, fn, params)
        return pickle.loads(rv)

    def call_plugin_serialized_with_retry(self, plugin, fn, num_retries,
                                          callback, retry_cb=None, *args,
                                          **kwargs):
        """Allows a plugin to raise RetryableError so we can try again."""
        attempts = num_retries + 1
        sleep_time = 0.5
        for attempt in range(1, attempts + 1):
            try:
                if attempt > 1:
                    time.sleep(sleep_time)
                    sleep_time = min(2 * sleep_time, 15)

                callback_result = None
                if callback:
                    callback_result = callback(kwargs)

                msg = ('%(plugin)s.%(fn)s attempt %(attempt)d/%(attempts)d, '
                       'callback_result: %(callback_result)s')
                LOG.debug(msg,
                          {'plugin': plugin, 'fn': fn, 'attempt': attempt,
                           'attempts': attempts,
                           'callback_result': callback_result})
                return self.call_plugin_serialized(plugin, fn, *args, **kwargs)
            except self.XenAPI.Failure as exc:
                if self._is_retryable_exception(exc, fn):
                    LOG.warning(_LW('%(plugin)s.%(fn)s failed. '
                                    'Retrying call.'),
                                {'plugin': plugin, 'fn': fn})
                    if retry_cb:
                        retry_cb(exc=exc)
                else:
                    raise
            except socket.error as exc:
                if exc.errno == errno.ECONNRESET:
                    LOG.warning(_LW('Lost connection to XenAPI during call to '
                                    '%(plugin)s.%(fn)s.  Retrying call.'),
                                {'plugin': plugin, 'fn': fn})
                    if retry_cb:
                        retry_cb(exc=exc)
                else:
                    raise

        raise exception.PluginRetriesExceeded(num_retries=num_retries)

    def _is_retryable_exception(self, exc, fn):
        _type, method, error = exc.details[:3]
        if error == 'RetryableError':
            LOG.debug("RetryableError, so retrying %(fn)s", {'fn': fn},
                      exc_info=True)
            return True
        elif "signal" in method:
            LOG.debug("Error due to a signal, retrying %(fn)s", {'fn': fn},
                      exc_info=True)
            return True
        else:
            return False

    def _create_session(self, url):
        """Stubout point. This can be replaced with a mock session."""
        self.is_local_connection = url == "unix://local"
        if self.is_local_connection:
            return self.XenAPI.xapi_local()
        return self.XenAPI.Session(url)

    def _unwrap_plugin_exceptions(self, func, *args, **kwargs):
        """Parse exception details."""
        try:
            return func(*args, **kwargs)
        except self.XenAPI.Failure as exc:
            LOG.debug("Got exception: %s", exc)
            if (len(exc.details) == 4 and
                exc.details[0] == 'XENAPI_PLUGIN_EXCEPTION' and
                    exc.details[2] == 'Failure'):
                params = None
                try:
                    # FIXME(comstud): eval is evil.
                    params = eval(exc.details[3])
                except Exception:
                    raise exc
                raise self.XenAPI.Failure(params)
            else:
                raise
        except xmlrpclib.ProtocolError as exc:
            LOG.debug("Got exception: %s", exc)
            raise

    def get_rec(self, record_type, ref):
        try:
            return self.call_xenapi('%s.get_record' % record_type, ref)
        except self.XenAPI.Failure as e:
            if e.details[0] != 'HANDLE_INVALID':
                raise

        return None

    def get_all_refs_and_recs(self, record_type):
        """Retrieve all refs and recs for a Xen record type.

        Handles race-conditions where the record may be deleted between
        the `get_all` call and the `get_record` call.
        """

        return self.call_xenapi('%s.get_all_records' % record_type).items()
<|endoftext|>


<commit_before>from zinc_request_processor import ZincRequestProcessor
import sys
import json

WELCOME_BANNER = """
 ____      ____             __
|_  _|    |_  _|           [  |
  \ \  /\  / /.---.  .---.  | |  .--.   _ .--..--.  .---.
   \ \/  \/ // /__\\/ /'`\] | |/ .'`\ \[ `.-. .-. |/ /__\\
    \  /\  / | \__.,| \__.  | || \__. | | | | | | || \__.,
     \/  \/   '.__.''.___.'[___]'.__.' [___||__||__]'.__.'

                     _________
                    |  _   _  |
                    |_/ | | \_|.--.
                        | |  / .'`\ \
                       _| |_ | \__. |
                      |_____| '.__.'

            ________   _                   _
           |  __   _| (_)                 | |
           |_/  / /   __   _ .--.   .---. | |
              .'.' _ [  | [ `.-. | / /'`\]| |
            _/ /__/ | | |  | | | | | \__. |_|
           |________|[___][___||__]'.___.'(_)
"""

class ValidationHelpers(object):
    @classmethod
    def validate_number(klass, maximum, minimum=0):
        def validate(x):
            if (int(x) <= maximum and int(x) >= minimum):
                return True
            else:
                print "You must enter a number between %s and %s\n" % (minimum, maximum)
        return validate

    @classmethod
    def validate_boolean(klass):
        def validate(x):
            if (x == "y" or x == "n"):
                return True
            else:
                print "You must enter either 'y' or 'n'"
        return validate

class ZincWizard(object):
    PROMPTS = {
        "product_variants": WELCOME_BANNER + "\nPlease please enter a product URL.",
        "product_quantity": "How many would you like to purchase? (Default: 1)",
        "select_product_variants": "This item comes in multiple variants. Please choose an option.",
        "select_shipping_methods": "This item has multiple shipping options. Please choose an option.",
        "security_code": "Please enter the CVV security code on your credit card.",
        "place_order": "Would you like to place this order? (y)/n",
        "gift": "Do you want this to be shipped as a gift? (y)/n",
        "shipping_address": {
            "start_message": "\nNow we'd like to get your shipping information.",
            "end_message": "\nYou've finished entering your shipping address!"
            },
        "address": {
            "first_name": "Please input your first name:",
            "last_name": "Please input your last name:",
            "address_line1": "Please input the first line of your shipping address:",
            "address_line2": "Please input the second line of your shipping address: (optional)",
            "city": "Please input your city:",
            "state": "Please input your state (e.g. CA, MA, etc.):",
            "zip_code": "Please input your zip code:",
            "country": "Please input your country (e.g. US):",
            "confirmation_message": "Is this your correct shipping address? (y)/n"
            },
        "billing_address" : {
            "start_message": "\nIs your billing address the same as your shipping address? (y)/n",
            "end_message": "\nYou've finished entering you billing address!"
            },
        "credit_card": {
            "start_message": "\nNow we'd like to get your credit card information.",
            "number": "Please input your credit card number",
            "expiration_month": "Please input your credit card expiration month (e.g. 03)",
            "expiration_year": "Please input your credit card expiration year (e.g. 2017)",
            "security_code": "Please input the CVV security code from your credit card",
            "end_message": "\nYou've finished entering your credit card information!"
            }
        }

    def __init__(self, options = None):
        self.options = (options if options is not None else {})
        self.response_data = {}
        self.security_code = None

    def start(self):
        self.get_product_variants(self.response_data)
        self.get_shipping_methods(self.response_data)
        self.get_store_card(self.response_data)
        self.get_review_order(self.response_data)
        self.get_place_order(self.response_data)

    def prompt(self, prompt, validation=None, max_attempts=3):
        attempts = 0
        while True:
            raw = raw_input(prompt + "\n")
            if (validation == None) or (validation != None and validation(raw)):
                return raw
            else:
                attempts += 1
                if attempts >= max_attempts:
                    print "You've reached the maximum number of attempts. Exiting!"
                    sys.exit()

    def prompt_boolean(self, prompt):
        result = self.prompt(prompt, ValidationHelpers.validate_boolean()).strip()
        if (result == "y" or result == ""):
            return True
        return False

    def get_product_variants(self, response_data):
        if "product_url" in self.options:
            product_url = self.options["product_url"]
        else:
            product_url = self.prompt(self.PROMPTS["product_variants"])

        print "\nProcessing request...\n"
        variants_response = ZincRequestProcessor.process("variant_options", {
                    "client_token": self.options["client_token"],
                    "retailer": self.options["retailer"],
                    "product_url": product_url
                    })
        response_data["variant_options_response"] = variants_response
        response_data["products"] = self.select_product_variants(variants_response)

    def get_shipping_methods(self, response_data):
        shipping_address = self.load_file_contents("shipping_address")
        print "\nProcessing request...\n"
        shipping_response = ZincRequestProcessor.process("shipping_methods", {
                    "client_token": self.options["client_token"],
                    "retailer": self.options["retailer"],
                    "products": response_data["products"],
                    "shipping_address": shipping_address
                })

        response_data["shipping_response"] = shipping_response
        response_data["shipping_address"] = shipping_address
        response_data["shipping_method_id"] = self.select_shipping_methods(shipping_response)

    def get_store_card(self, response_data):
        if "cc_token" in self.options and self.options["cc_token"] != None:
            response_data["cc_token"] = self.options["cc_token"]
        else:
            cc_data = self.load_file_contents("credit_card")
            print "\nProcessing request...\n"
            store_card_response = ZincRequestProcessor.process("store_card", {
                        "client_token": self.options["client_token"],
                        "retailer": self.options["retailer"],
                        "billing_address": self.load_file_contents("billing_address"),
                        "number": cc_data["number"],
                        "expiration_month": cc_data["expiration_month"],
                        "expiration_year": cc_data["expiration_year"]
                    })

            response_data["store_card_response"] = store_card_response
            response_data["cc_token"] = store_card_response["cc_token"]

    def get_review_order(self, response_data):
        payment_method = {
                "cc_token": response_data["cc_token"],
                "security_code": self.get_security_code()
                }
        print "\nProcessing request...\n"
        review_order_response = ZincRequestProcessor.process("review_order", {
                    "client_token": self.options["client_token"],
                    "retailer": self.options["retailer"],
                    "products": response_data["products"],
                    "shipping_address": response_data["shipping_address"],
                    "is_gift": self.get_is_gift(),
                    "shipping_method_id": response_data["shipping_method_id"],
                    "payment_method": payment_method,
                    "customer_email": "support@zinc.io"
                })

        response_data["review_order_response"] = review_order_response

    def get_place_order(self, response_data):
        self.print_price_components(response_data)
        if self.prompt_boolean(self.PROMPTS["place_order"]):
            print "\nProcessing request...\n"
            place_order_resonse = ZincRequestProcessor.process("place_order", {
                        "client_token": self.options["client_token"],
                        "place_order_key": response_data["review_order_response"]["place_order_key"]
                    })

            response_data["place_order_response"] = place_order_response
            print "HOORAY! You've successfully placed an order. Here are the details:\n"
            print "Amazon Order Id: %s" % place_order_response["merchant_order"]["merchant_order_id"]
            print "Total Price: %s" % place_order_response["price_components"]["total"]
            print place_order_response["shipping_method"]["name"] + ": " + place_order_response["shipping_method"]["description"]

    def print_price_components(self, response_data):
        components = response_data["review_order_response"]["price_components"]
        print "Product Subtotal: %s" % components["subtotal"]
        print "Shipping Cost:    %s" % components["shipping"]
        print "Tax:              %s" % components["tax"]
        print "Gift Card:        %s" % components["gift_card"]
        print "Total:            %s" % components["total"]

    def get_is_gift(self):
        if "gift" in self.options:
            return self.options["gift"]
        return self.prompt_boolean(self.PROMPTS["gift"])

    def get_security_code(self):
        if self.security_code != None:
            return self.security_code
        return self.prompt(self.PROMPTS["security_code"])

    def load_file_contents(self, filetype):
        if filetype in self.options and self.options[filetype] != None:
            with open(self.options[filetype], 'rb') as f:
                return json.loads(f.read())
        elif filetype == "shipping_address":
            print self.PROMPTS[filetype]["start_message"]
            return self.get_address(filetype)
        elif filetype == "billing_address":
            if self.prompt_boolean(self.PROMPTS[filetype]["start_message"]):
                return self.response_data["shipping_address"]
            else:
                return self.get_address(filetype)
        elif filetype == "credit_card":
            return self.get_credit_card_information()

    def get_credit_card_information(self):
        print self.PROMPTS["credit_card"]["start_message"]
        response = {}
        response["number"] = self.prompt(self.PROMPTS["credit_card"]["number"])
        response["expiration_month"] = self.prompt(self.PROMPTS["credit_card"]["expiration_month"])
        response["expiration_year"] = self.prompt(self.PROMPTS["credit_card"]["expiration_year"])
        self.security_code = self.prompt(self.PROMPTS["credit_card"]["security_code"])
        print self.PROMPTS["credit_card"]["end_message"]
        return response

    def get_address(self, filetype):
        address = {}
        for label in ["first_name", "last_name", "address_line1", "address_line2",
                "city", "state", "zip_code", "country"]:
            address[label] = self.prompt(self.PROMPTS["address"][label])

        print "\nYou typed the following:\n"
        print "    %s %s" % (address["first_name"], address["last_name"])
        print "    %s" % (address["address_line1"])
        print "    %s" % (address["address_line2"])
        print "    %s, %s %s" % (address["city"], address["state"], address["zip_code"])
        print "    %s" % (address["country"])
        print ""

        if self.prompt_boolean(self.PROMPTS["address"]["confirmation_message"]):
            print self.PROMPTS[filetype]["end_message"]
            return address
        else:
            self.get_address(filetype)

    def build_prompt(self, base_prompt, description_list):
        prompt = base_prompt + "\n"
        prompt += "\n".join(description_list)
        return prompt

    def select_product_variants(self, variants_response):
        descriptions = []
        product_ids = []
        if (len(variants_response["variant_options"]) > 1):
            for i in xrange(len(variants_response["variant_options"])):
                current_descriptions_list = []
                current_option = variants_response["variant_options"][i]
                for dimension in current_option["dimensions"]:
                    current_descriptions_list.append(dimension["name"] + ": " + dimension["value"])
                if "unit_price" in current_option:
                    current_descriptions_list.append("Price: " + self.format_price(current_option["unit_price"]))
                product_ids.append(current_option["product_id"])
                descriptions.append(str(i) + ") " + ", ".join(current_descriptions_list))

            prompt = self.build_prompt(self.PROMPTS["select_product_variants"], descriptions)

            description_number = self.prompt(prompt,
                    ValidationHelpers.validate_number(len(descriptions)))
            chosen_product_id = product_ids[int(description_number)]
        else:
            chosen_product_id = variants_response["variant_options"][0]["product_id"]

        quantity = self.get_quantity()
        return [{
                "product_id": chosen_product_id,
                "quantity": quantity
                }]

    def format_price(self, price):
        if len(price) > 2:
            return "$" + price[:(-2)] + "." + price[(-2):]
        elif len(price) == 2:
            return "$0." + price
        elif len(price) == 1:
            return "$0.0" + price

    def get_quantity(self):
        quantity = self.prompt(self.PROMPTS["product_quantity"]).strip()
        if quantity == "":
            return 1
        else:
            return quantity

    def select_shipping_methods(self, shipping_response):
        descriptions = []
        shipping_ids = []
        for i in xrange(len(shipping_response["shipping_methods"])):
            current_method = shipping_response["shipping_methods"][i]
            descriptions.append(str(i) + ") " + current_method["name"] + \
                    ": " + current_method["description"])
            shipping_ids.append(current_method["shipping_method_id"])

        prompt = self.build_prompt(self.PROMPTS["select_shipping_methods"], descriptions)
        description_number = self.prompt(prompt,
                ValidationHelpers.validate_number(len(descriptions)))
        chosen_id = shipping_ids[int(description_number)]
        return chosen_id

if __name__ == '__main__':
    ZincWizard({'retailer': 'amazon',
        'client_token': 'public',
        #'shipping_address': "examples/shipping_address.json",
        #'billing_address': "examples/shipping_address.json",
        #'credit_card': "examples/credit_card.json"
        }).start()
<commit_msg>Creating a print indent method.<commit_after>from zinc_request_processor import ZincRequestProcessor
import sys
import json

WELCOME_BANNER = """
 ____      ____             __
|_  _|    |_  _|           [  |
  \ \  /\  / /.---.  .---.  | |  .--.   _ .--..--.  .---.
   \ \/  \/ // /__\\/ /'`\] | |/ .'`\ \[ `.-. .-. |/ /__\\
    \  /\  / | \__.,| \__.  | || \__. | | | | | | || \__.,
     \/  \/   '.__.''.___.'[___]'.__.' [___||__||__]'.__.'

                     _________
                    |  _   _  |
                    |_/ | | \_|.--.
                        | |  / .'`\ \
                       _| |_ | \__. |
                      |_____| '.__.'

            ________   _                   _
           |  __   _| (_)                 | |
           |_/  / /   __   _ .--.   .---. | |
              .'.' _ [  | [ `.-. | / /'`\]| |
            _/ /__/ | | |  | | | | | \__. |_|
           |________|[___][___||__]'.___.'(_)
"""

class ValidationHelpers(object):
    @classmethod
    def validate_number(klass, maximum, minimum=0):
        def validate(x):
            if (int(x) <= maximum and int(x) >= minimum):
                return True
            else:
                print "You must enter a number between %s and %s\n" % (minimum, maximum)
        return validate

    @classmethod
    def validate_boolean(klass):
        def validate(x):
            if (x == "y" or x == "n"):
                return True
            else:
                print "You must enter either 'y' or 'n'"
        return validate

class ZincWizard(object):
    PROMPTS = {
        "product_variants": WELCOME_BANNER + "\nPlease please enter a product URL.",
        "product_quantity": "How many would you like to purchase? (Default: 1)",
        "select_product_variants": "This item comes in multiple variants. Please choose an option.",
        "select_shipping_methods": "This item has multiple shipping options. Please choose an option.",
        "security_code": "Please enter the CVV security code on your credit card.",
        "place_order": "Would you like to place this order? (y)/n",
        "gift": "Do you want this to be shipped as a gift? (y)/n",
        "shipping_address": {
            "start_message": "\nNow we'd like to get your shipping information.",
            "end_message": "\nYou've finished entering your shipping address!"
            },
        "address": {
            "first_name": "Please input your first name:",
            "last_name": "Please input your last name:",
            "address_line1": "Please input the first line of your shipping address:",
            "address_line2": "Please input the second line of your shipping address: (optional)",
            "city": "Please input your city:",
            "state": "Please input your state (e.g. CA, MA, etc.):",
            "zip_code": "Please input your zip code:",
            "country": "Please input your country (e.g. US):",
            "confirmation_message": "Is this your correct shipping address? (y)/n"
            },
        "billing_address" : {
            "start_message": "\nIs your billing address the same as your shipping address? (y)/n",
            "end_message": "\nYou've finished entering you billing address!"
            },
        "credit_card": {
            "start_message": "\nNow we'd like to get your credit card information.",
            "number": "Please input your credit card number",
            "expiration_month": "Please input your credit card expiration month (e.g. 03)",
            "expiration_year": "Please input your credit card expiration year (e.g. 2017)",
            "security_code": "Please input the CVV security code from your credit card",
            "end_message": "\nYou've finished entering your credit card information!"
            }
        }

    def __init__(self, options = None):
        self.options = (options if options is not None else {})
        self.response_data = {}
        self.security_code = None

    def start(self):
        self.get_product_variants(self.response_data)
        self.get_shipping_methods(self.response_data)
        self.get_store_card(self.response_data)
        self.get_review_order(self.response_data)
        self.get_place_order(self.response_data)

    def prompt(self, prompt, validation=None, max_attempts=3):
        attempts = 0
        while True:
            raw = raw_input(prompt + "\n")
            if (validation == None) or (validation != None and validation(raw)):
                return raw
            else:
                attempts += 1
                if attempts >= max_attempts:
                    print "You've reached the maximum number of attempts. Exiting!"
                    sys.exit()

    def prompt_boolean(self, prompt):
        result = self.prompt(prompt, ValidationHelpers.validate_boolean()).strip()
        if (result == "y" or result == ""):
            return True
        return False

    def get_product_variants(self, response_data):
        if "product_url" in self.options:
            product_url = self.options["product_url"]
        else:
            product_url = self.prompt(self.PROMPTS["product_variants"])

        print "\nProcessing request...\n"
        variants_response = ZincRequestProcessor.process("variant_options", {
                    "client_token": self.options["client_token"],
                    "retailer": self.options["retailer"],
                    "product_url": product_url
                    })
        response_data["variant_options_response"] = variants_response
        response_data["products"] = self.select_product_variants(variants_response)

    def get_shipping_methods(self, response_data):
        shipping_address = self.load_file_contents("shipping_address")
        print "\nProcessing request...\n"
        shipping_response = ZincRequestProcessor.process("shipping_methods", {
                    "client_token": self.options["client_token"],
                    "retailer": self.options["retailer"],
                    "products": response_data["products"],
                    "shipping_address": shipping_address
                })

        response_data["shipping_response"] = shipping_response
        response_data["shipping_address"] = shipping_address
        response_data["shipping_method_id"] = self.select_shipping_methods(shipping_response)

    def get_store_card(self, response_data):
        if "cc_token" in self.options and self.options["cc_token"] != None:
            response_data["cc_token"] = self.options["cc_token"]
        else:
            cc_data = self.load_file_contents("credit_card")
            print "\nProcessing request...\n"
            store_card_response = ZincRequestProcessor.process("store_card", {
                        "client_token": self.options["client_token"],
                        "retailer": self.options["retailer"],
                        "billing_address": self.load_file_contents("billing_address"),
                        "number": cc_data["number"],
                        "expiration_month": cc_data["expiration_month"],
                        "expiration_year": cc_data["expiration_year"]
                    })

            response_data["store_card_response"] = store_card_response
            response_data["cc_token"] = store_card_response["cc_token"]

    def get_review_order(self, response_data):
        payment_method = {
                "cc_token": response_data["cc_token"],
                "security_code": self.get_security_code()
                }
        print "\nProcessing request...\n"
        review_order_response = ZincRequestProcessor.process("review_order", {
                    "client_token": self.options["client_token"],
                    "retailer": self.options["retailer"],
                    "products": response_data["products"],
                    "shipping_address": response_data["shipping_address"],
                    "is_gift": self.get_is_gift(),
                    "shipping_method_id": response_data["shipping_method_id"],
                    "payment_method": payment_method,
                    "customer_email": "support@zinc.io"
                })

        response_data["review_order_response"] = review_order_response

    def get_place_order(self, response_data):
        self.print_price_components(response_data)
        if self.prompt_boolean(self.PROMPTS["place_order"]):
            print "\nProcessing request...\n"
            place_order_resonse = ZincRequestProcessor.process("place_order", {
                        "client_token": self.options["client_token"],
                        "place_order_key": response_data["review_order_response"]["place_order_key"]
                    })

            response_data["place_order_response"] = place_order_response
            print "HOORAY! You've successfully placed an order. Here are the details:\n"
            print "Amazon Order Id: %s" % place_order_response["merchant_order"]["merchant_order_id"]
            print "Total Price: %s" % place_order_response["price_components"]["total"]
            print place_order_response["shipping_method"]["name"] + ": " + place_order_response["shipping_method"]["description"]

    def print_price_components(self, response_data):
        components = response_data["review_order_response"]["price_components"]
        self.print_indent("Product Subtotal: %s" % self.format_price(components["subtotal"]))
        self.print_indent("Shipping Cost:    %s" % self.format_price(components["shipping"]))
        self.print_indent("Tax:              %s" % self.format_price(components["tax"]))
        if "gift_card" in components:
            self.print_indent("Gift Card:        %s" % self.format_price(components["gift_card"]))
        self.print_indent("Total:            %s" % self.format_price(components["total"]))

    def print_indent(self, value):
        print "    ", value

    def get_is_gift(self):
        if "gift" in self.options:
            return self.options["gift"]
        return self.prompt_boolean(self.PROMPTS["gift"])

    def get_security_code(self):
        if self.security_code != None:
            return self.security_code
        return self.prompt(self.PROMPTS["security_code"])

    def load_file_contents(self, filetype):
        if filetype in self.options and self.options[filetype] != None:
            with open(self.options[filetype], 'rb') as f:
                return json.loads(f.read())
        elif filetype == "shipping_address":
            print self.PROMPTS[filetype]["start_message"]
            return self.get_address(filetype)
        elif filetype == "billing_address":
            if self.prompt_boolean(self.PROMPTS[filetype]["start_message"]):
                return self.response_data["shipping_address"]
            else:
                return self.get_address(filetype)
        elif filetype == "credit_card":
            return self.get_credit_card_information()

    def get_credit_card_information(self):
        print self.PROMPTS["credit_card"]["start_message"]
        response = {}
        response["number"] = self.prompt(self.PROMPTS["credit_card"]["number"])
        response["expiration_month"] = self.prompt(self.PROMPTS["credit_card"]["expiration_month"])
        response["expiration_year"] = self.prompt(self.PROMPTS["credit_card"]["expiration_year"])
        self.security_code = self.prompt(self.PROMPTS["credit_card"]["security_code"])
        print self.PROMPTS["credit_card"]["end_message"]
        return response

    def get_address(self, filetype):
        address = {}
        for label in ["first_name", "last_name", "address_line1", "address_line2",
                "city", "state", "zip_code", "country"]:
            address[label] = self.prompt(self.PROMPTS["address"][label])

        print "\nYou typed the following:\n"
        self.print_indent("    %s %s" % (address["first_name"], address["last_name"]))
        self.print_indent("    %s" % (address["address_line1"]))
        self.print_indent("    %s" % (address["address_line2"]))
        self.print_indent("    %s, %s %s" % (address["city"], address["state"], address["zip_code"]))
        self.print_indent("    %s" % (address["country"]))
        print ""

        if self.prompt_boolean(self.PROMPTS["address"]["confirmation_message"]):
            print self.PROMPTS[filetype]["end_message"]
            return address
        else:
            self.get_address(filetype)

    def build_prompt(self, base_prompt, description_list):
        prompt = base_prompt + "\n"
        prompt += "\n".join(description_list)
        return prompt

    def select_product_variants(self, variants_response):
        descriptions = []
        product_ids = []
        if (len(variants_response["variant_options"]) > 1):
            for i in xrange(len(variants_response["variant_options"])):
                current_descriptions_list = []
                current_option = variants_response["variant_options"][i]
                for dimension in current_option["dimensions"]:
                    current_descriptions_list.append(dimension["name"] + ": " + dimension["value"])
                if "unit_price" in current_option:
                    current_descriptions_list.append("Price: " + self.format_price(current_option["unit_price"]))
                product_ids.append(current_option["product_id"])
                descriptions.append(str(i) + ") " + ", ".join(current_descriptions_list))

            prompt = self.build_prompt(self.PROMPTS["select_product_variants"], descriptions)

            description_number = self.prompt(prompt,
                    ValidationHelpers.validate_number(len(descriptions)))
            chosen_product_id = product_ids[int(description_number)]
        else:
            chosen_product_id = variants_response["variant_options"][0]["product_id"]

        quantity = self.get_quantity()
        return [{
                "product_id": chosen_product_id,
                "quantity": quantity
                }]

    def format_price(self, price):
        if len(price) > 2:
            return "$" + price[:(-2)] + "." + price[(-2):]
        elif len(price) == 2:
            return "$0." + price
        elif len(price) == 1:
            return "$0.0" + price

    def get_quantity(self):
        quantity = self.prompt(self.PROMPTS["product_quantity"]).strip()
        if quantity == "":
            return 1
        else:
            return quantity

    def select_shipping_methods(self, shipping_response):
        descriptions = []
        shipping_ids = []
        for i in xrange(len(shipping_response["shipping_methods"])):
            current_method = shipping_response["shipping_methods"][i]
            descriptions.append(str(i) + ") " + current_method["name"] + \
                    ": " + current_method["description"])
            shipping_ids.append(current_method["shipping_method_id"])

        prompt = self.build_prompt(self.PROMPTS["select_shipping_methods"], descriptions)
        description_number = self.prompt(prompt,
                ValidationHelpers.validate_number(len(descriptions)))
        chosen_id = shipping_ids[int(description_number)]
        return chosen_id

if __name__ == '__main__':
    ZincWizard({'retailer': 'amazon',
        'client_token': 'public',
        #'shipping_address': "examples/shipping_address.json",
        #'billing_address': "examples/shipping_address.json",
        #'credit_card': "examples/credit_card.json"
        }).start()
<|endoftext|>