<commit_before>""" A collection of helper functions for consuming the Travis CI API. """

# Standard library
import base64
import json
from os.path import dirname, join
import re

# 3rd party library
import requests
import rsa
import yaml

class TravisUtils(object):
    """ A collection of helper functions for consuming the Travis CI API. """

    @staticmethod
    def enable_hook(repo_id, token):
        """ Enable the travis hook for the repository with the given id. """

        payload  = json.dumps(dict(hook=dict(active=True, id=repo_id)))
        headers  = TravisUtils.get_header(token)
        url      = 'https://api.travis-ci.org/hooks/%s' % repo_id
        response = requests.put(url, data=payload,  headers=headers)

        return response.status_code == 200

    @staticmethod
    def get_access_token(github_token):

        url = 'https://api.travis-ci.org/auth/github'
        data = {'github_token': github_token}

        return requests.post(url, data=data).json().get('access_token')

    @staticmethod
    def get_encrypted_text(repo_name, data):
        """ Return encrypted text for the data. """

        public_key = TravisUtils.get_public_key(repo_name)
        key = rsa.PublicKey.load_pkcs1_openssl_pem(public_key)
        secure = base64.encodestring(rsa.encrypt(data, key))
        secure, _ = re.subn('\s+', '', secure)

        return secure

    @staticmethod
    def get_header(token):
        """ Return a header with authorization info, given the token. """

        return {
            'Authorization': 'token %s' % token,
            'Content-Type': 'application/json; charset=UTF-8'
        }

    @staticmethod
    def get_public_key(repo):
        """ Get a public key for the repository from travis. """

        url = 'https://api.travis-ci.org/repos/%s' % repo
        response = requests.get(url)

        return response.json()['public_key'].replace('RSA PUBLIC', 'PUBLIC')

    @staticmethod
    def get_repo_id(full_name, token):
        """ Get the id for a repository from travis. """

        if TravisUtils.hook_exists(full_name, token):
            url      = 'https://api.travis-ci.org/repos/%s' % full_name
            response = requests.get(url).json()
            repo_id = response.get('id')

        else:
            repo_id = None

        return repo_id

    @staticmethod
    def get_script_contents(full_name):
        """ Get the contents of the script to be run on travis. """

        template = join(
            dirname(__file__), 'utils', 'travis_script_template.sh'
        )
        with open(template) as f:
            contents = f.read()

        return contents % {'REPO': full_name}

    @staticmethod
    def get_status():
        """ Return the server status of GitHub. """

        response = requests.get('http://status.travis-ci.com')
        pattern  = '(
((.|\s)*?)
)'

        return re.findall(pattern, response.text)[0][1].strip()

    @staticmethod
    def get_yaml_contents(full_name, github_token):
        """ Get the contents to be dumped into .travis.yml. """

        data   = 'github_token=%s GIT_NAME=%s GIT_EMAIL=%s' % (
            github_token.encode(), 'Travis CI', 'testing@travis-ci.org'
        )
        secure = TravisUtils.get_encrypted_text(full_name, data)
        config = {
            'env': {'global': {'secure': secure}},
            'before_install': 'sudo apt-get install -qq python-lxml',
            'install': 'pip install nikola webassets',
            'branches': {'only': ['master']},
            'language': 'python',
            'python': ['2.7'],
            'script': 'bash %(SCRIPT)s',
            'virtualenv': {'system_site_packages': True},
        }

        return yaml.dump(config)

    @staticmethod
    def hook_exists(full_name, token):
        """ Return True if a hook for the repository is listed on travis. """

        headers  = {
            'Authorization': 'token %s' % token,
        }
        response = requests.get(
            'http://api.travis-ci.org/hooks', headers=headers
        )

        owner, name = full_name.split('/')
        if response.status_code == 200:
            repositories = [
                repo for repo in response.json()
                if repo['name'] == name and repo['owner_name'] == owner
            ]
            hook_exists = len(repositories) > 0

        else:
            hook_exists = False

        return hook_exists

    @staticmethod
    def is_travis_user(github_token):
        """ Check if a user is a Travis user.

        Return the travis token if so, else None.

        """

        travis_token = TravisUtils.get_access_token(github_token)
        headers      = {
            'Authorization': 'token %s' % travis_token,
            'Content-Type': 'application/json; charset=UTF-8'
        }

        response = requests.get(
            'https://api.travis-ci.org/users/', headers=headers
        )

        if response.status_code == 200:
            synced_at = response.json().get('synced_at')
        else:
            synced_at = None

        return travis_token if synced_at is not None else None

    @staticmethod
    def start_sync(token):
        """ Start syncing repositories for the user with the given token. """

        headers  = TravisUtils.get_header(token)
        response = requests.post(
            'http://api.travis-ci.org/users/sync', headers=headers
        )

        return (
            response.json()['result'] if response.status_code == 200
            else False
        )

    @staticmethod
    def sync_with_github(token):
        """ Sync the repositories of the user on Travis from GitHub. """

        if TravisUtils.start_sync(token):
            synced = TravisUtils.wait_to_sync(token)
        else:
            synced = False

        return synced

    @staticmethod
    def wait_to_sync(headers):
        """ Wait until a sync finishes. """

        import time

        for count in range(6):

            response = requests.get(
                'http://api.travis-ci.org/users/', headers=headers
            )

            if response.status_code == 200:
                if response.json()['is_syncing']:
                    finished = False
                    time.sleep(2 ** count)
                else:
                    finished = True
                    break
            else:
                finished = False
                break

        return finished
<commit_msg>Fix the yaml config file in the travis.yml.<commit_after>""" A collection of helper functions for consuming the Travis CI API. """

# Standard library
import base64
import json
from os.path import dirname, join
import re

# 3rd party library
import requests
import rsa
import yaml

class TravisUtils(object):
    """ A collection of helper functions for consuming the Travis CI API. """

    @staticmethod
    def enable_hook(repo_id, token):
        """ Enable the travis hook for the repository with the given id. """

        payload  = json.dumps(dict(hook=dict(active=True, id=repo_id)))
        headers  = TravisUtils.get_header(token)
        url      = 'https://api.travis-ci.org/hooks/%s' % repo_id
        response = requests.put(url, data=payload,  headers=headers)

        return response.status_code == 200

    @staticmethod
    def get_access_token(github_token):

        url = 'https://api.travis-ci.org/auth/github'
        data = {'github_token': github_token}

        return requests.post(url, data=data).json().get('access_token')

    @staticmethod
    def get_encrypted_text(repo_name, data):
        """ Return encrypted text for the data. """

        public_key = TravisUtils.get_public_key(repo_name)
        key = rsa.PublicKey.load_pkcs1_openssl_pem(public_key)
        secure = base64.encodestring(rsa.encrypt(data, key))
        secure, _ = re.subn('\s+', '', secure)

        return secure

    @staticmethod
    def get_header(token):
        """ Return a header with authorization info, given the token. """

        return {
            'Authorization': 'token %s' % token,
            'Content-Type': 'application/json; charset=UTF-8'
        }

    @staticmethod
    def get_public_key(repo):
        """ Get a public key for the repository from travis. """

        url = 'https://api.travis-ci.org/repos/%s' % repo
        response = requests.get(url)

        return response.json()['public_key'].replace('RSA PUBLIC', 'PUBLIC')

    @staticmethod
    def get_repo_id(full_name, token):
        """ Get the id for a repository from travis. """

        if TravisUtils.hook_exists(full_name, token):
            url      = 'https://api.travis-ci.org/repos/%s' % full_name
            response = requests.get(url).json()
            repo_id = response.get('id')

        else:
            repo_id = None

        return repo_id

    @staticmethod
    def get_script_contents(full_name):
        """ Get the contents of the script to be run on travis. """

        template = join(
            dirname(__file__), 'utils', 'travis_script_template.sh'
        )
        with open(template) as f:
            contents = f.read()

        return contents % {'REPO': full_name}

    @staticmethod
    def get_status():
        """ Return the server status of GitHub. """

        response = requests.get('http://status.travis-ci.com')
        pattern  = '(
((.|\s)*?)
)'

        return re.findall(pattern, response.text)[0][1].strip()

    @staticmethod
    def get_yaml_contents(full_name, github_token):
        """ Get the contents to be dumped into .travis.yml. """

        data   = 'github_token=%s GIT_NAME=%s GIT_EMAIL=%s' % (
            github_token.encode(), 'Travis CI', 'testing@travis-ci.org'
        )
        secure = TravisUtils.get_encrypted_text(full_name, data)

        config = {
            'env': {'global': {'secure': secure}},
            'install': [
                'wget https://github.com/getnikola/wheelhouse/archive/v2.7.zip',
                'unzip v2.7.zip',
                'pip install --use-wheel --no-index --find-links=wheelhouse-2.7 lxml Pillow',
                'rm -rf wheelhouse-2.7 v2.7.zip',
                'pip install nikola webassets',
            ],
            'branches': {'only': ['master']},
            'language': 'python',
            'python': ['2.7'],
            'script': 'bash %(SCRIPT)s',
        }

        return yaml.dump(config)

    @staticmethod
    def hook_exists(full_name, token):
        """ Return True if a hook for the repository is listed on travis. """

        headers  = {
            'Authorization': 'token %s' % token,
        }
        response = requests.get(
            'http://api.travis-ci.org/hooks', headers=headers
        )

        owner, name = full_name.split('/')
        if response.status_code == 200:
            repositories = [
                repo for repo in response.json()
                if repo['name'] == name and repo['owner_name'] == owner
            ]
            hook_exists = len(repositories) > 0

        else:
            hook_exists = False

        return hook_exists

    @staticmethod
    def is_travis_user(github_token):
        """ Check if a user is a Travis user.

        Return the travis token if so, else None.

        """

        travis_token = TravisUtils.get_access_token(github_token)
        headers      = {
            'Authorization': 'token %s' % travis_token,
            'Content-Type': 'application/json; charset=UTF-8'
        }

        response = requests.get(
            'https://api.travis-ci.org/users/', headers=headers
        )

        if response.status_code == 200:
            synced_at = response.json().get('synced_at')
        else:
            synced_at = None

        return travis_token if synced_at is not None else None

    @staticmethod
    def start_sync(token):
        """ Start syncing repositories for the user with the given token. """

        headers  = TravisUtils.get_header(token)
        response = requests.post(
            'http://api.travis-ci.org/users/sync', headers=headers
        )

        return (
            response.json()['result'] if response.status_code == 200
            else False
        )

    @staticmethod
    def sync_with_github(token):
        """ Sync the repositories of the user on Travis from GitHub. """

        if TravisUtils.start_sync(token):
            synced = TravisUtils.wait_to_sync(token)
        else:
            synced = False

        return synced

    @staticmethod
    def wait_to_sync(headers):
        """ Wait until a sync finishes. """

        import time

        for count in range(6):

            response = requests.get(
                'http://api.travis-ci.org/users/', headers=headers
            )

            if response.status_code == 200:
                if response.json()['is_syncing']:
                    finished = False
                    time.sleep(2 ** count)
                else:
                    finished = True
                    break
            else:
                finished = False
                break

        return finished
<|endoftext|>


<commit_before>#pylint: disable=C0301
#pylint: disable=R0904

import os
import re
import osgtest.library.core as core
import osgtest.library.files as files
import osgtest.library.osgunittest as osgunittest

class TestCondorCE(osgunittest.OSGTestCase):
    def general_requirements(self):
        core.skip_ok_unless_installed('condor', 'htcondor-ce', 'htcondor-ce-client')
        self.skip_bad_unless('condor-ce.started', 'ce not running')

    def test_01_status(self):
        self.general_requirements()

        command = ('condor_ce_status', '-verbose')
        core.check_system(command, 'ce status', user=True)

    def test_02_queue(self):
        self.general_requirements()

        command = ('condor_ce_q', '-verbose')
        core.check_system(command, 'ce queue', user=True)

    def test_03_ping(self):
        self.general_requirements()

        command = ('condor_ce_ping', 'WRITE', '-verbose')
        stdout, _, _ = core.check_system(command, 'ping using GSI and gridmap', user=True)
        self.assert_(re.search(r'Authorized:\s*TRUE', stdout), 'could not authorize with GSI')

    def test_04_trace(self):
        self.general_requirements()

        cwd = os.getcwd()
        os.chdir('/tmp')

        command = ('condor_ce_trace', '--debug', core.get_hostname())
        core.check_system(command, 'ce trace', user=True)

        os.chdir(cwd)

    def test_05_pbs_trace(self):
        self.general_requirements()
        core.skip_ok_unless_installed('torque-mom', 'torque-server', 'torque-scheduler', 'torque-client', 'munge')
        self.skip_ok_unless(core.state['torque.pbs-server-running'])

        cwd = os.getcwd()
        os.chdir('/tmp')

        command = ('condor_ce_trace', '-a osgTestPBS = True', '--debug', core.get_hostname())
        core.check_system(command, 'ce trace against pbs', user=True)

        os.chdir(cwd)

    def test_06_use_gums_auth(self):
        self.general_requirements()
        core.skip_ok_unless_installed('gums-service')

        # Setting up GUMS auth using the instructions here:
        # twiki.grid.iu.edu/bin/view/Documentation/Release3/InstallComputeElement#8_1_Using_GUMS_for_Authorization
        hostname = core.get_hostname()

        lcmaps_contents = '''gumsclient = "lcmaps_gums_client.mod"
             "-resourcetype ce"
             "-actiontype execute-now"
             "-capath /etc/grid-security/certificates"
             "-cert   /etc/grid-security/hostcert.pem"
             "-key    /etc/grid-security/hostkey.pem"
             "--cert-owner root"
# Change this URL to your GUMS server
             "--endpoint https://%s:8443/gums/services/GUMSXACMLAuthorizationServicePort"

verifyproxy = "lcmaps_verify_proxy.mod"
          "--allow-limited-proxy"
          " -certdir /etc/grid-security/certificates"

# lcmaps policies require at least two modules, so these are here to
#   fill in if only one module is needed.  "good | bad" has no effect.
good        = "lcmaps_dummy_good.mod"
bad         = "lcmaps_dummy_bad.mod"

authorize_only:
## Policy 1: GUMS but not SAZ (most common, default)
gumsclient -> good | bad
''' % hostname

        gums_properties_contents = '''gums.location=https://%s:8443/gums/services/GUMSAdmin
gums.authz=https://%s:8443/gums/services/GUMSXACMLAuthorizationServicePort
''' % (hostname, hostname)

        core.config['condor-ce.gums-properties'] = '/etc/gums/gums-client.properties'
        core.config['condor-ce.gsi-authz'] = '/etc/grid-security/gsi-authz.conf'

        files.write(core.config['condor-ce.lcmapsdb'], lcmaps_contents, owner='condor-ce.gums')
        files.write(core.config['condor-ce.gums-properties'], gums_properties_contents, owner='condor-ce')
        files.replace(core.config['condor-ce.gsi-authz'],
                      '# globus_mapping liblcas_lcmaps_gt4_mapping.so lcmaps_callout',
                      'globus_mapping liblcas_lcmaps_gt4_mapping.so lcmaps_callout',
                      owner='condor-ce')

        command = ('service', 'condor-ce', 'stop')
        core.check_system(command, 'stop condor-ce')

        # Need to stat the Schedd logfile so we know when it's back up
        core.config['condor-ce.schedlog'] = '/var/log/condor-ce/SchedLog'
        core.config['condor-ce.schedlog-stat'] = os.stat(core.config['condor-ce.schedlog'])

        command = ('service', 'condor-ce', 'start')
        core.check_system(command, 'start condor-ce')

    def test_07_ping_with_gums(self):
        self.general_requirements()
        core.skip_ok_unless_installed('gums-service')

        # Wait for the collector to come back up
        core.monitor_file(core.config['condor-ce.schedlog'],
                          core.config['condor-ce.schedlog-stat'],
                          'TransferQueueManager stats',
                          60.0)

        command = ('condor_ce_ping', 'WRITE', '-verbose')
        stdout, _, _ = core.check_system(command, 'ping using GSI and gridmap', user=True)
        self.assert_(re.search(r'Authorized:\s*TRUE', stdout), 'could not authorize with GSI')

<commit_msg>BadSkip HTCondor CE tests if the service failed to start (SOFTWARE-1898)<commit_after>#pylint: disable=C0301
#pylint: disable=R0904

import os
import re
import osgtest.library.core as core
import osgtest.library.files as files
import osgtest.library.osgunittest as osgunittest

class TestCondorCE(osgunittest.OSGTestCase):
    def general_requirements(self):
        core.skip_ok_unless_installed('condor', 'htcondor-ce', 'htcondor-ce-client')
        self.skip_bad_unless(core.state['condor-ce.started'], 'ce not running')

    def test_01_status(self):
        self.general_requirements()

        command = ('condor_ce_status', '-verbose')
        core.check_system(command, 'ce status', user=True)

    def test_02_queue(self):
        self.general_requirements()

        command = ('condor_ce_q', '-verbose')
        core.check_system(command, 'ce queue', user=True)

    def test_03_ping(self):
        self.general_requirements()

        command = ('condor_ce_ping', 'WRITE', '-verbose')
        stdout, _, _ = core.check_system(command, 'ping using GSI and gridmap', user=True)
        self.assert_(re.search(r'Authorized:\s*TRUE', stdout), 'could not authorize with GSI')

    def test_04_trace(self):
        self.general_requirements()

        cwd = os.getcwd()
        os.chdir('/tmp')

        command = ('condor_ce_trace', '--debug', core.get_hostname())
        core.check_system(command, 'ce trace', user=True)

        os.chdir(cwd)

    def test_05_pbs_trace(self):
        self.general_requirements()
        core.skip_ok_unless_installed('torque-mom', 'torque-server', 'torque-scheduler', 'torque-client', 'munge')
        self.skip_ok_unless(core.state['torque.pbs-server-running'])

        cwd = os.getcwd()
        os.chdir('/tmp')

        command = ('condor_ce_trace', '-a osgTestPBS = True', '--debug', core.get_hostname())
        core.check_system(command, 'ce trace against pbs', user=True)

        os.chdir(cwd)

    def test_06_use_gums_auth(self):
        self.general_requirements()
        core.skip_ok_unless_installed('gums-service')

        # Setting up GUMS auth using the instructions here:
        # twiki.grid.iu.edu/bin/view/Documentation/Release3/InstallComputeElement#8_1_Using_GUMS_for_Authorization
        hostname = core.get_hostname()

        lcmaps_contents = '''gumsclient = "lcmaps_gums_client.mod"
             "-resourcetype ce"
             "-actiontype execute-now"
             "-capath /etc/grid-security/certificates"
             "-cert   /etc/grid-security/hostcert.pem"
             "-key    /etc/grid-security/hostkey.pem"
             "--cert-owner root"
# Change this URL to your GUMS server
             "--endpoint https://%s:8443/gums/services/GUMSXACMLAuthorizationServicePort"

verifyproxy = "lcmaps_verify_proxy.mod"
          "--allow-limited-proxy"
          " -certdir /etc/grid-security/certificates"

# lcmaps policies require at least two modules, so these are here to
#   fill in if only one module is needed.  "good | bad" has no effect.
good        = "lcmaps_dummy_good.mod"
bad         = "lcmaps_dummy_bad.mod"

authorize_only:
## Policy 1: GUMS but not SAZ (most common, default)
gumsclient -> good | bad
''' % hostname

        gums_properties_contents = '''gums.location=https://%s:8443/gums/services/GUMSAdmin
gums.authz=https://%s:8443/gums/services/GUMSXACMLAuthorizationServicePort
''' % (hostname, hostname)

        core.config['condor-ce.gums-properties'] = '/etc/gums/gums-client.properties'
        core.config['condor-ce.gsi-authz'] = '/etc/grid-security/gsi-authz.conf'

        files.write(core.config['condor-ce.lcmapsdb'], lcmaps_contents, owner='condor-ce.gums')
        files.write(core.config['condor-ce.gums-properties'], gums_properties_contents, owner='condor-ce')
        files.replace(core.config['condor-ce.gsi-authz'],
                      '# globus_mapping liblcas_lcmaps_gt4_mapping.so lcmaps_callout',
                      'globus_mapping liblcas_lcmaps_gt4_mapping.so lcmaps_callout',
                      owner='condor-ce')

        command = ('service', 'condor-ce', 'stop')
        core.check_system(command, 'stop condor-ce')

        # Need to stat the Schedd logfile so we know when it's back up
        core.config['condor-ce.schedlog'] = '/var/log/condor-ce/SchedLog'
        core.config['condor-ce.schedlog-stat'] = os.stat(core.config['condor-ce.schedlog'])

        command = ('service', 'condor-ce', 'start')
        core.check_system(command, 'start condor-ce')

    def test_07_ping_with_gums(self):
        self.general_requirements()
        core.skip_ok_unless_installed('gums-service')

        # Wait for the collector to come back up
        core.monitor_file(core.config['condor-ce.schedlog'],
                          core.config['condor-ce.schedlog-stat'],
                          'TransferQueueManager stats',
                          60.0)

        command = ('condor_ce_ping', 'WRITE', '-verbose')
        stdout, _, _ = core.check_system(command, 'ping using GSI and gridmap', user=True)
        self.assert_(re.search(r'Authorized:\s*TRUE', stdout), 'could not authorize with GSI')

<|endoftext|>


<commit_before>#!/usr/bin/env python2.7

"""
triangulizor.py

Applies a "triangular pixel" effect to images. Inspired by:
 * http://revdancatt.com/2012/03/31/the-pxl-effect-with-javascript-and-canvas-and-maths/
 * https://github.com/revdancatt/GuardianAmbientHeadlineRadio
"""

import argparse
from cStringIO import StringIO
import logging
import os
import re
import sys
import urllib2

import Image
import ImageDraw

def triangulize(image, tile_size):
    """Processes the given image by breaking it down into tiles of the given
    size and applying a triangular effect to each tile. Returns the processed
    image.
    """
    assert isinstance(image, Image.Image), type(image)
    assert isinstance(tile_size, int) and tile_size % 2 == 0
    # Preprocess image to make sure it's at a size we can handle
    image = prep_image(image, tile_size)
    # Get pixmap (for direct pixel access) and draw objects for the image.
    pix = image.load()
    draw = ImageDraw.Draw(image)
    for x, y in iter_tiles(image, tile_size):
        process_tile(x, y, tile_size, pix, draw, image)
    return image

def process_tile(tile_x, tile_y, tile_size, pix, draw, image):
    """Process a tile whose top left corner is at the given x and y
    coordinates.
    """
    # Calculate average color for each "triangle" in the given tile
    n, e, s, w = triangle_colors(tile_x, tile_y, tile_size, pix)

    # Calculate distance between triangle pairs
    d_ne = get_color_dist(n, e)
    d_nw = get_color_dist(n, w)
    d_se = get_color_dist(s, e)
    d_sw = get_color_dist(s, w)

    # Figure out which pair is the closest, which will determine the direction
    # we'll split this tile into triangles. A 'right' split runs from top left
    # to bottom right. A 'left' split runs bottom left to top right.
    closest = sorted([d_ne, d_nw, d_se, d_sw])[0]
    if closest in (d_ne, d_sw):
        split = 'right'
    elif closest in (d_nw, d_se):
        split = 'left'

    # Figure out the average color for each side of the "split"
    if split == 'right':
        top_color = get_average_color([n, e])
        bottom_color = get_average_color([s, w])
    else:
        top_color = get_average_color([n, w])
        bottom_color = get_average_color([s, e])

    draw_triangles(tile_x, tile_y, tile_size, split, top_color, bottom_color,
                   draw)

def triangle_colors(tile_x, tile_y, tile_size, pix):
    """Extracts the average color for each triangle in the given tile. Returns
    a 4-tuple of colors for the triangles in this order: North, East, South,
    West (clockwise).
    """
    quad_size = tile_size / 2

    north = []
    for y in xrange(tile_y, tile_y + quad_size):
        x_off = y - tile_y
        for x in xrange(tile_x + x_off, tile_x + tile_size - x_off):
            north.append(pix[x,y])

    south = []
    for y in xrange(tile_y + quad_size, tile_y + tile_size):
        x_off = tile_y + tile_size - y
        for x in xrange(tile_x + x_off, tile_x + tile_size - x_off):
            south.append(pix[x,y])

    east = []
    for x in xrange(tile_x, tile_x + quad_size):
        y_off = x - tile_x
        for y in xrange(tile_y + y_off, tile_y + tile_size - y_off):
            east.append(pix[x, y])

    west = []
    for x in xrange(tile_x + quad_size, tile_x + tile_size):
        y_off = tile_x + tile_size - x
        for y in xrange(tile_y + y_off, tile_y + tile_size - y_off):
            west.append(pix[x, y])

    return map(get_average_color, [north, east, south, west])

def draw_triangles(tile_x, tile_y, tile_size, split, top_color, bottom_color,
                   draw):
    """Draws a triangle on each half of the tile with the given coordinates
    and size.
    """
    assert split in ('right', 'left')

    # The four corners of this tile
    nw = (tile_x, tile_y)
    ne = (tile_x + tile_size - 1, tile_y)
    se = (tile_x + tile_size - 1, tile_y + tile_size)
    sw = (tile_x, tile_y + tile_size)

    if split == 'right':
        # top right triangle
        draw_triangle(nw, ne, se, top_color, draw)
        # bottom left triangle
        draw_triangle(nw, sw, se, bottom_color, draw)
    else:
        # top left triangle
        draw_triangle(sw, nw, ne, top_color, draw)
        # bottom right triangle
        draw_triangle(sw, se, ne, bottom_color, draw)

def draw_triangle(a, b, c, color, draw):
    """Draws a triangle with the given vertices in the given color."""
    draw.polygon([a, b, c], fill=make_color(color))

def get_average_color(colors):
    """Calculate the average color from the list of colors, where each color
    is a 3-tuple of (r, g, b) values.
    """
    tr, tg, tb = reduce(color_reducer, colors)
    total = len(colors)
    return tr/total, tg/total, tb/total

def color_reducer((r1, g1, b1), (r2, g2, b2)):
    """Helper function used to add two colors together when averaging."""
    return r1+r2, g1+g2, b1+b2

def get_color_dist((r1, g1, b1), (r2, g2, b2)):
    """Calculates the "distance" between two colors, where the distance is
    another color whose components are the absolute values of the difference
    between each component of the input colors.
    """
    return (abs(r1-r2), abs(g1-g2), abs(b1-b2))

def make_color((r, g, b)):
    """Formats a color for use in an ImageDraw method."""
    return 'rgb(%d, %d, %d)' % (r, g, b)

def prep_image(image, tile_size):
    """Takes an image and a tile size and returns a possibly cropped version
    of the image that is evenly divisible in both dimensions by the tile size.
    """
    w, h = image.size
    x_tiles = w / tile_size # floor division
    y_tiles = h / tile_size
    new_w = x_tiles * tile_size
    new_h = y_tiles * tile_size
    if new_w == w and new_h == h:
        return image
    else:
        crop_bounds = (0, 0, new_w, new_h)
        return image.crop(crop_bounds)

def iter_tiles(image, tile_size):
    """Yields (x, y) coordinate pairs for the top left corner of each tile in
    the given image, based on the given tile size.
    """
    w, h = image.size
    for y in xrange(0, h, tile_size):
        for x in xrange(0, w, tile_size):
            yield x, y

if __name__ == '__main__':

    def path_or_url(x):
        if re.match(r'^https?://', x):
            try:
                return urllib2.urlopen(x)
            except urllib2.URLError, e:
                raise argparse.ArgumentTypeError(str(e))
        elif os.path.isfile(x):
            return open(x, 'rb')
        else:
            msg = '%r not found' % x
            raise argparse.ArgumentTypeError(msg)

    arg_parser = argparse.ArgumentParser(
        description='Applies a "triangular pixel" effect to an image.')
    arg_parser.add_argument(
        'infile', nargs='?', default=sys.stdin, type=path_or_url,
        help='Image to process (path or URL; defaults to STDIN)')
    arg_parser.add_argument(
        'outfile', nargs='?', default=sys.stdout,
        type=argparse.FileType('wb'))
    arg_parser.add_argument(
        '-t', '--tile-size', type=int, default=20,
        help='Tile size (must be divisible by 2; defaults to 20)')
    arg_parser.add_argument(
        '-o', '--output',help='Output path (defaults to STDOUT)')

    args = arg_parser.parse_args()

    inbuffer = StringIO(args.infile.read())
    image = triangulize(Image.open(inbuffer), args.tile_size)
    image.save(args.outfile, 'png')
    image.show()
<commit_msg>Add logging/verbosity options<commit_after>#!/usr/bin/env python2.7

"""
triangulizor.py

Applies a "triangular pixel" effect to images. Inspired by:
 * http://revdancatt.com/2012/03/31/the-pxl-effect-with-javascript-and-canvas-and-maths/
 * https://github.com/revdancatt/GuardianAmbientHeadlineRadio
"""

import argparse
from cStringIO import StringIO
import logging
import os
import re
import sys
import urllib2

import Image
import ImageDraw

def triangulize(image, tile_size):
    """Processes the given image by breaking it down into tiles of the given
    size and applying a triangular effect to each tile. Returns the processed
    image.
    """
    assert isinstance(image, Image.Image), type(image)
    assert isinstance(tile_size, int) and tile_size % 2 == 0
    logging.info('Input image size: %r', image.size)
    logging.info('Tile size: %r', tile_size)
    # Preprocess image to make sure it's at a size we can handle
    image = prep_image(image, tile_size)
    logging.info('Prepped image size: %r', image.size)
    # Get pixmap (for direct pixel access) and draw objects for the image.
    pix = image.load()
    draw = ImageDraw.Draw(image)
    for x, y in iter_tiles(image, tile_size):
        process_tile(x, y, tile_size, pix, draw, image)
    return image

def process_tile(tile_x, tile_y, tile_size, pix, draw, image):
    """Process a tile whose top left corner is at the given x and y
    coordinates.
    """
    logging.debug('Processing tile (%d, %d)', tile_x, tile_y)

    # Calculate average color for each "triangle" in the given tile
    n, e, s, w = triangle_colors(tile_x, tile_y, tile_size, pix)

    # Calculate distance between triangle pairs
    d_ne = get_color_dist(n, e)
    d_nw = get_color_dist(n, w)
    d_se = get_color_dist(s, e)
    d_sw = get_color_dist(s, w)

    # Figure out which pair is the closest, which will determine the direction
    # we'll split this tile into triangles. A 'right' split runs from top left
    # to bottom right. A 'left' split runs bottom left to top right.
    closest = sorted([d_ne, d_nw, d_se, d_sw])[0]
    if closest in (d_ne, d_sw):
        split = 'right'
    elif closest in (d_nw, d_se):
        split = 'left'

    # Figure out the average color for each side of the "split"
    if split == 'right':
        top_color = get_average_color([n, e])
        bottom_color = get_average_color([s, w])
    else:
        top_color = get_average_color([n, w])
        bottom_color = get_average_color([s, e])

    draw_triangles(tile_x, tile_y, tile_size, split, top_color, bottom_color,
                   draw)

def triangle_colors(tile_x, tile_y, tile_size, pix):
    """Extracts the average color for each triangle in the given tile. Returns
    a 4-tuple of colors for the triangles in this order: North, East, South,
    West (clockwise).
    """
    quad_size = tile_size / 2

    north = []
    for y in xrange(tile_y, tile_y + quad_size):
        x_off = y - tile_y
        for x in xrange(tile_x + x_off, tile_x + tile_size - x_off):
            north.append(pix[x,y])

    south = []
    for y in xrange(tile_y + quad_size, tile_y + tile_size):
        x_off = tile_y + tile_size - y
        for x in xrange(tile_x + x_off, tile_x + tile_size - x_off):
            south.append(pix[x,y])

    east = []
    for x in xrange(tile_x, tile_x + quad_size):
        y_off = x - tile_x
        for y in xrange(tile_y + y_off, tile_y + tile_size - y_off):
            east.append(pix[x, y])

    west = []
    for x in xrange(tile_x + quad_size, tile_x + tile_size):
        y_off = tile_x + tile_size - x
        for y in xrange(tile_y + y_off, tile_y + tile_size - y_off):
            west.append(pix[x, y])

    return map(get_average_color, [north, east, south, west])

def draw_triangles(tile_x, tile_y, tile_size, split, top_color, bottom_color,
                   draw):
    """Draws a triangle on each half of the tile with the given coordinates
    and size.
    """
    assert split in ('right', 'left')

    # The four corners of this tile
    nw = (tile_x, tile_y)
    ne = (tile_x + tile_size - 1, tile_y)
    se = (tile_x + tile_size - 1, tile_y + tile_size)
    sw = (tile_x, tile_y + tile_size)

    if split == 'right':
        # top right triangle
        draw_triangle(nw, ne, se, top_color, draw)
        # bottom left triangle
        draw_triangle(nw, sw, se, bottom_color, draw)
    else:
        # top left triangle
        draw_triangle(sw, nw, ne, top_color, draw)
        # bottom right triangle
        draw_triangle(sw, se, ne, bottom_color, draw)

def draw_triangle(a, b, c, color, draw):
    """Draws a triangle with the given vertices in the given color."""
    draw.polygon([a, b, c], fill=make_color(color))

def get_average_color(colors):
    """Calculate the average color from the list of colors, where each color
    is a 3-tuple of (r, g, b) values.
    """
    tr, tg, tb = reduce(color_reducer, colors)
    total = len(colors)
    return tr/total, tg/total, tb/total

def color_reducer((r1, g1, b1), (r2, g2, b2)):
    """Helper function used to add two colors together when averaging."""
    return r1+r2, g1+g2, b1+b2

def get_color_dist((r1, g1, b1), (r2, g2, b2)):
    """Calculates the "distance" between two colors, where the distance is
    another color whose components are the absolute values of the difference
    between each component of the input colors.
    """
    return (abs(r1-r2), abs(g1-g2), abs(b1-b2))

def make_color((r, g, b)):
    """Formats a color for use in an ImageDraw method."""
    return 'rgb(%d, %d, %d)' % (r, g, b)

def prep_image(image, tile_size):
    """Takes an image and a tile size and returns a possibly cropped version
    of the image that is evenly divisible in both dimensions by the tile size.
    """
    w, h = image.size
    x_tiles = w / tile_size # floor division
    y_tiles = h / tile_size
    new_w = x_tiles * tile_size
    new_h = y_tiles * tile_size
    if new_w == w and new_h == h:
        return image
    else:
        crop_bounds = (0, 0, new_w, new_h)
        return image.crop(crop_bounds)

def iter_tiles(image, tile_size):
    """Yields (x, y) coordinate pairs for the top left corner of each tile in
    the given image, based on the given tile size.
    """
    w, h = image.size
    for y in xrange(0, h, tile_size):
        for x in xrange(0, w, tile_size):
            yield x, y

if __name__ == '__main__':

    def path_or_url(x):
        if re.match(r'^https?://', x):
            try:
                return urllib2.urlopen(x)
            except urllib2.URLError, e:
                raise argparse.ArgumentTypeError(str(e))
        elif os.path.isfile(x):
            return open(x, 'rb')
        else:
            msg = '%r not found' % x
            raise argparse.ArgumentTypeError(msg)

    arg_parser = argparse.ArgumentParser(
        description='Applies a "triangular pixel" effect to an image.')
    arg_parser.add_argument(
        'infile', nargs='?', default=sys.stdin, type=path_or_url,
        help='Image to process (path or URL; defaults to STDIN)')
    arg_parser.add_argument(
        'outfile', nargs='?', default=sys.stdout,
        type=argparse.FileType('wb'))
    arg_parser.add_argument(
        '-t', '--tile-size', type=int, default=20,
        help='Tile size (must be divisible by 2; defaults to 20)')
    arg_parser.add_argument(
        '-o', '--output',help='Output path (defaults to STDOUT)')
    arg_parser.add_argument(
        '-v', '--verbose', default=False, action='store_const', const=True,
        help='Verbose output')
    arg_parser.add_argument(
        '-vv', default=False, action='store_const', const=True,
        help='Very verbose output')

    args = arg_parser.parse_args()

    if args.verbose or args.vv:
        logger = logging.getLogger()
        logger.setLevel(logging.DEBUG if args.vv else logging.INFO)

    inbuffer = StringIO(args.infile.read())
    image = triangulize(Image.open(inbuffer), args.tile_size)
    image.save(args.outfile, 'png')
    image.show()
<|endoftext|>


<commit_before># emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
""" General matlab interface code """
import os

from nipype.interfaces.base import (CommandLineInputSpec, InputMultiPath, isdefined,
                                    CommandLine, traits, File, Directory)
from nipype.utils.config import config

class MatlabInputSpec(CommandLineInputSpec):
    """ Basic expected inputs to Matlab interface """

    script  = traits.Str(argstr='-r \"%s;exit\"', desc='m-code to run',
                         mandatory=True, position=-2)
    uses_mcr = traits.Bool(desc='use MCR interface',
                           xor=['nodesktop', 'nosplash',
                                'single_comp_thread'])
    nodesktop = traits.Bool(True, argstr='-nodesktop',
                            usedefault=True,
                            desc='Switch off desktop mode on unix platforms')
    nosplash = traits.Bool(True, argstr='-nosplash', usedefault=True,
                           descr='Switch of splash screen')
    logfile = File(argstr='-logfile %s',
                          desc='Save matlab output to log')
    single_comp_thread = traits.Bool(argstr="-singleCompThread",
                                   desc="force single threaded operation")
    # non-commandline options
    mfile   = traits.Bool(True, desc='Run m-code using m-file',
                          usedefault=True)
    script_file = File('pyscript.m', usedefault=True,
                              desc='Name of file to write m-code to')
    paths   = InputMultiPath(Directory(), desc='Paths to add to matlabpath')
    prescript = traits.List(["ver,","try,"], usedefault=True,
                            desc='prescript to be added before code')
    postscript = traits.List(["\n,catch ME,",
                              "fprintf(2,'MATLAB code threw an exception:\\n');",
                              "fprintf(2,'%s\\n',ME.message);",
                              "if length(ME.stack) ~= 0, fprintf(2,'File:%s\\nName:%s\\nLine:%d\\n',ME.stack.file,ME.stack.name,ME.stack.line);, end;",
                              "end;"], desc='script added after code', usedefault = True)
    reset_stty = traits.Enum(';stty echo', usedefault=True, argstr='%s', position=-1)

class MatlabCommand(CommandLine):
    """Interface that runs matlab code

    >>> import nipype.interfaces.matlab as matlab
    >>> mlab = matlab.MatlabCommand()
    >>> mlab.inputs.script = "which('who')"
    >>> out = mlab.run() # doctest: +SKIP
    """

    _cmd = 'matlab'
    _default_matlab_cmd = None
    _default_mfile = None
    _default_paths = None
    input_spec = MatlabInputSpec

    def __init__(self, matlab_cmd = None, **inputs):
        """initializes interface to matlab
        (default 'matlab -nodesktop -nosplash')
        """
        super(MatlabCommand,self).__init__(**inputs)
        if matlab_cmd and isdefined(matlab_cmd):
            self._cmd = matlab_cmd
        elif self._default_matlab_cmd:
            self._cmd = self._default_matlab_cmd

        if self._default_mfile and not isdefined(self.inputs.mfile):
            self.inputs.mfile = self._default_mfile

        if self._default_paths and not isdefined(self.inputs.paths):
            self.inputs.paths = self._default_paths

        if not isdefined(self.inputs.single_comp_thread) and \
                not isdefined(self.inputs.uses_mcr):
            if config.getboolean('execution','single_thread_matlab'):
                self.inputs.single_comp_thread = True

    @classmethod
    def set_default_matlab_cmd(cls, matlab_cmd):
        """Set the default MATLAB command line for MATLAB classes.

        This method is used to set values for all MATLAB
        subclasses.  However, setting this will not update the output
        type for any existing instances.  For these, assign the
        <instance>.inputs.matlab_cmd.
        """
        cls._default_matlab_cmd = matlab_cmd

    @classmethod
    def set_default_mfile(cls, mfile):
        """Set the default MATLAB script file format for MATLAB classes.

        This method is used to set values for all MATLAB
        subclasses.  However, setting this will not update the output
        type for any existing instances.  For these, assign the
        <instance>.inputs.mfile.
        """
        cls._default_mfile = mfile

    @classmethod
    def set_default_paths(cls, paths):
        """Set the default MATLAB paths for MATLAB classes.

        This method is used to set values for all MATLAB
        subclasses.  However, setting this will not update the output
        type for any existing instances.  For these, assign the
        <instance>.inputs.paths.
        """
        cls._default_paths = paths

    def _run_interface(self,runtime):
        runtime = super(MatlabCommand, self)._run_interface(runtime)
        if 'MATLAB code threw an exception' in runtime.stderr:
            self.raise_exception(runtime)
        return runtime

    def _format_arg(self, name, trait_spec, value):
        if name in ['script']:
            argstr = trait_spec.argstr
            if self.inputs.uses_mcr:
                argstr='%s'
            return self._gen_matlab_command(argstr, value)
        return super(MatlabCommand, self)._format_arg(name, trait_spec, value)

    def _gen_matlab_command(self, argstr, script_lines):
        cwd = os.getcwd()
        mfile = self.inputs.mfile or self.inputs.uses_mcr
        paths = []
        if isdefined(self.inputs.paths):
            paths = self.inputs.paths
        # prescript
        prescript = self.inputs.prescript
        postscript = self.inputs.postscript

        #postcript takes different default value depending on the mfile argument
        if mfile:
            prescript.insert(0,"fprintf(1,'Executing %s at %s:\\n',mfilename,datestr(now));")
        else:
            prescript.insert(0,"fprintf(1,'Executing code at %s:\\n',datestr(now));")
        for path in paths:
            prescript.append("addpath('%s');\n" % path)

        if not mfile:
            #clean up the code of comments and replace newlines with commas
            script_lines = ','.join([line for line in script_lines.split("\n") if not line.strip().startswith("%")])

        script_lines = '\n'.join(prescript)+script_lines+'\n'.join(postscript)
        if mfile:
            mfile = file(os.path.join(cwd,self.inputs.script_file), 'wt')
            mfile.write(script_lines)
            mfile.close()
            if self.inputs.uses_mcr:
                script = '%s' % (os.path.join(cwd,self.inputs.script_file))
            else:
                script = "addpath('%s');%s" % (cwd, self.inputs.script_file.split('.')[0])
        else:
            script = ''.join(script_lines.split('\n'))
        return argstr % script
<commit_msg>fix: removed stty reset from matlab - should really be part of the commandline class<commit_after># emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-
# vi: set ft=python sts=4 ts=4 sw=4 et:
""" General matlab interface code """
import os

from nipype.interfaces.base import (CommandLineInputSpec, InputMultiPath, isdefined,
                                    CommandLine, traits, File, Directory)
from nipype.utils.config import config

class MatlabInputSpec(CommandLineInputSpec):
    """ Basic expected inputs to Matlab interface """

    script  = traits.Str(argstr='-r \"%s;exit\"', desc='m-code to run',
                         mandatory=True, position=-1)
    uses_mcr = traits.Bool(desc='use MCR interface',
                           xor=['nodesktop', 'nosplash',
                                'single_comp_thread'])
    nodesktop = traits.Bool(True, argstr='-nodesktop',
                            usedefault=True,
                            desc='Switch off desktop mode on unix platforms')
    nosplash = traits.Bool(True, argstr='-nosplash', usedefault=True,
                           descr='Switch of splash screen')
    logfile = File(argstr='-logfile %s',
                          desc='Save matlab output to log')
    single_comp_thread = traits.Bool(argstr="-singleCompThread",
                                   desc="force single threaded operation")
    # non-commandline options
    mfile   = traits.Bool(True, desc='Run m-code using m-file',
                          usedefault=True)
    script_file = File('pyscript.m', usedefault=True,
                              desc='Name of file to write m-code to')
    paths   = InputMultiPath(Directory(), desc='Paths to add to matlabpath')
    prescript = traits.List(["ver,","try,"], usedefault=True,
                            desc='prescript to be added before code')
    postscript = traits.List(["\n,catch ME,",
                              "fprintf(2,'MATLAB code threw an exception:\\n');",
                              "fprintf(2,'%s\\n',ME.message);",
                              "if length(ME.stack) ~= 0, fprintf(2,'File:%s\\nName:%s\\nLine:%d\\n',ME.stack.file,ME.stack.name,ME.stack.line);, end;",
                              "end;"], desc='script added after code', usedefault = True)

class MatlabCommand(CommandLine):
    """Interface that runs matlab code

    >>> import nipype.interfaces.matlab as matlab
    >>> mlab = matlab.MatlabCommand()
    >>> mlab.inputs.script = "which('who')"
    >>> out = mlab.run() # doctest: +SKIP
    """

    _cmd = 'matlab'
    _default_matlab_cmd = None
    _default_mfile = None
    _default_paths = None
    input_spec = MatlabInputSpec

    def __init__(self, matlab_cmd = None, **inputs):
        """initializes interface to matlab
        (default 'matlab -nodesktop -nosplash')
        """
        super(MatlabCommand,self).__init__(**inputs)
        if matlab_cmd and isdefined(matlab_cmd):
            self._cmd = matlab_cmd
        elif self._default_matlab_cmd:
            self._cmd = self._default_matlab_cmd

        if self._default_mfile and not isdefined(self.inputs.mfile):
            self.inputs.mfile = self._default_mfile

        if self._default_paths and not isdefined(self.inputs.paths):
            self.inputs.paths = self._default_paths

        if not isdefined(self.inputs.single_comp_thread) and \
                not isdefined(self.inputs.uses_mcr):
            if config.getboolean('execution','single_thread_matlab'):
                self.inputs.single_comp_thread = True

    @classmethod
    def set_default_matlab_cmd(cls, matlab_cmd):
        """Set the default MATLAB command line for MATLAB classes.

        This method is used to set values for all MATLAB
        subclasses.  However, setting this will not update the output
        type for any existing instances.  For these, assign the
        <instance>.inputs.matlab_cmd.
        """
        cls._default_matlab_cmd = matlab_cmd

    @classmethod
    def set_default_mfile(cls, mfile):
        """Set the default MATLAB script file format for MATLAB classes.

        This method is used to set values for all MATLAB
        subclasses.  However, setting this will not update the output
        type for any existing instances.  For these, assign the
        <instance>.inputs.mfile.
        """
        cls._default_mfile = mfile

    @classmethod
    def set_default_paths(cls, paths):
        """Set the default MATLAB paths for MATLAB classes.

        This method is used to set values for all MATLAB
        subclasses.  However, setting this will not update the output
        type for any existing instances.  For these, assign the
        <instance>.inputs.paths.
        """
        cls._default_paths = paths

    def _run_interface(self,runtime):
        runtime = super(MatlabCommand, self)._run_interface(runtime)
        if 'MATLAB code threw an exception' in runtime.stderr:
            self.raise_exception(runtime)
        return runtime

    def _format_arg(self, name, trait_spec, value):
        if name in ['script']:
            argstr = trait_spec.argstr
            if self.inputs.uses_mcr:
                argstr='%s'
            return self._gen_matlab_command(argstr, value)
        return super(MatlabCommand, self)._format_arg(name, trait_spec, value)

    def _gen_matlab_command(self, argstr, script_lines):
        cwd = os.getcwd()
        mfile = self.inputs.mfile or self.inputs.uses_mcr
        paths = []
        if isdefined(self.inputs.paths):
            paths = self.inputs.paths
        # prescript
        prescript = self.inputs.prescript
        postscript = self.inputs.postscript

        #postcript takes different default value depending on the mfile argument
        if mfile:
            prescript.insert(0,"fprintf(1,'Executing %s at %s:\\n',mfilename,datestr(now));")
        else:
            prescript.insert(0,"fprintf(1,'Executing code at %s:\\n',datestr(now));")
        for path in paths:
            prescript.append("addpath('%s');\n" % path)

        if not mfile:
            #clean up the code of comments and replace newlines with commas
            script_lines = ','.join([line for line in script_lines.split("\n") if not line.strip().startswith("%")])

        script_lines = '\n'.join(prescript)+script_lines+'\n'.join(postscript)
        if mfile:
            mfile = file(os.path.join(cwd,self.inputs.script_file), 'wt')
            mfile.write(script_lines)
            mfile.close()
            if self.inputs.uses_mcr:
                script = '%s' % (os.path.join(cwd,self.inputs.script_file))
            else:
                script = "addpath('%s');%s" % (cwd, self.inputs.script_file.split('.')[0])
        else:
            script = ''.join(script_lines.split('\n'))
        return argstr % script
<|endoftext|>


<commit_before>import base64
import datetime
import json
import os
import subprocess

import requests
from Crypto.Util import asn1
from django.conf import settings

try:
    from simplejson.decoder import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError

__all__ = [
    'InvalidReceipt',
    'NoActiveReceiptException',
    'NoPurchasesException',
    'ReceiptValidationException',
    'parse_receipt',
    'validate_debug_receipt',
    'validate_production_receipt',
    'validate_receipt_with_apple',
    'validate_receipt_is_active',
]

CA_FILE = settings.IAP_SETTINGS['CA_FILE']

IAP_SHARED_SECRET = settings.IAP_SETTINGS.get('SHARED_SECRET')

PRODUCTION_VERIFICATION_URL = 'https://buy.itunes.apple.com/verifyReceipt'
SANDBOX_VERIFICATION_URL = 'https://sandbox.itunes.apple.com/verifyReceipt'

PRODUCTION_BUNDLE_ID = settings.IAP_SETTINGS['PRODUCTION_BUNDLE_ID']
DEBUG_BUNDLE_ID = settings.IAP_SETTINGS.get('DEBUG_BUNDLE_ID')

PRODUCTION_PRODUCT_IDS = settings.IAP_SETTINGS.get(
    'PRODUCTION_PRODUCT_IDS', set())
DEBUG_PRODUCT_IDS = settings.IAP_SETTINGS.get(
    'DEBUG_PRODUCT_IDS', set())

class InvalidReceipt(Exception):
    pass

class ReceiptValidationException(Exception):
    def __init__(self, receipt, *args, **kwargs):
        self.receipt = receipt
        super(ReceiptValidationException, self).__init__(*args, **kwargs)

class RetryReceiptValidation(ReceiptValidationException):
    pass

class NoActiveReceiptException(ReceiptValidationException):
    pass

class NoPurchasesException(ReceiptValidationException):
    pass

def verify_receipt_sig(raw_data):
    proc = subprocess.Popen([
        'openssl', 'smime', '-verify',
        '-inform', 'der',
        '-CAfile', CA_FILE,
        '-binary',
    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    signed_data, err_data = proc.communicate(raw_data)
    if proc.wait() != os.EX_OK:
        raise InvalidReceipt('Signature verification failed:\n' + err_data)
    return signed_data

def decode_obj(data, expected_type):
    der = asn1.DerObject()
    der.decode(data)
    if der.typeTag != expected_type:
        raise InvalidReceipt('Expected tag type {}; got {}'.format(
            expected_type, der.typeTag))
    return der.payload

def decode_seq_set(data):
    result = []
    offset = 0
    while offset < len(data):
        der = asn1.DerSequence()
        length = der.decode(data[offset:])
        offset += length
        result.append(list(der))
    return result

def decode_ia5(data):
    return decode_obj(data, 22).decode('ascii')

def decode_utf8(data):
    return decode_obj(data, 12).decode('utf-8')

def decode_int(data):
    der = asn1.DerInteger()
    der.decode(data)
    return der.value

def decode_receipt(data):
    # See https://developer.apple.com/library/ios/releasenotes/General/
    #   ValidateAppStoreReceipt/Chapters/ReceiptFields.html
    fields = {
        0: ('_environment', decode_utf8),
        2: ('bundle_id', decode_utf8),
        3: ('application_version', decode_utf8),
        # 4: ('_opaque_value', lambda x: x),
        # 5: ('_sha1_hash', lambda x: x),
        17: ('in_app', decode_receipt),
        19: ('original_application_version', decode_utf8),
        21: ('expiration_date', decode_ia5),
        1701: ('quantity', decode_int),
        1702: ('product_id', decode_utf8),
        1703: ('transaction_id', decode_utf8),
        1704: ('purchase_date', decode_ia5),
        1705: ('original_transaction_id', decode_utf8),
        1706: ('original_purchase_date', decode_ia5),
        1708: ('expires_date', decode_ia5),
        1711: ('web_order_line_item_id', decode_int),
        1712: ('cancellation_date', decode_ia5),
    }
    list_fields = [17]

    payload = decode_obj(data, 49)
    result = {}
    for attr_type, attr_version, attr_raw_value in decode_seq_set(payload):
        attr_value = decode_obj(attr_raw_value, 4)

        name, decoder = fields.get(attr_type, (None, None))
        if attr_type in list_fields:
            result.setdefault(name, []).append(decoder(attr_value))
        elif name is not None:
            result[name] = decoder(attr_value)
        # else:
        #    result['_unknown_{}'.format(attr_type)] = attr_value

    result['_sandbox'] = (
        result.get('original_application_version') == '1.0' and
        result.get('_environment') != 'Production')

    return result

def parse_receipt(raw_data):
    return decode_receipt(verify_receipt_sig(raw_data))

def validate_receipt_with_apple(data):
    payload = {'receipt-data': base64.b64encode(data)}

    if IAP_SHARED_SECRET:
        payload['password'] = IAP_SHARED_SECRET

    # Docs at http://goo.gl/WV5U63
    for url in (PRODUCTION_VERIFICATION_URL, SANDBOX_VERIFICATION_URL):
        r = requests.post(url, data=json.dumps(payload))
        r.raise_for_status()
        try:
            content = r.json()
        except JSONDecodeError:
            raise ReceiptValidationException({}, 'Unable to read response')

        if 'status' not in content:
            raise ReceiptValidationException(content, 'Unknown response format')
        status = content.get('status', 21000)
        if status == 21000:
            # The App Store could not read the JSON object you provided.
            raise ReceiptValidationException(content, 'Unable to read payload')
        elif status == 21002:
            # The data in the receipt-data property was malformed or missing.
            raise ReceiptValidationException(content, 'Malformed receipt-data')
        elif status == 21003:
            # The receipt could not be authenticated.
            raise ReceiptValidationException(
                content, 'Receipt is from an unknown source')
        elif status == 21004:
            # Bad shared secret for the app / auth failed
            # NOTE: Only returned for iOS 6 style transaction receipts for
            # auto-renewable subscriptions.
            raise ReceiptValidationException(
                content, 'The shared secret does not match one on file')
        elif status == 21005:
            # The receipt server is not currently available.
            raise ReceiptValidationException(content, 'WebObjects')
        elif status == 21006:
            # The receipt is inactive
            # NOTE: Only returned for iOS 6 style transaction receipts for
            # auto-renewable subscriptions. For iOS 7 style app receipts, the
            # status code is reflects the status of the app receipt as a whole.
            # For example, if you send a valid app receipt that contains an
            # expired subscription, the response is 0 because the receipt as a
            # whole is valid.
            raise ReceiptValidationException(content, 'Inactive subscription')
        elif status == 21007:
            if url == PRODUCTION_VERIFICATION_URL:
                # We need to try the other environment
                continue
            raise ReceiptValidationException(content, 'Cannot try another url!')
        elif status == 21008:
            raise ReceiptValidationException(content, 'We already tried prod!')
        elif status == 21009:
            # This seems to be an internal Apple error. In this case,
            # one should retry the request
            raise RetryReceiptValidation(content, 'Internal Apple error. Retry')
        elif status == 21010:
            # This receipt could not be authorized. Treated as if the purchase
            # was never made.
            raise NoPurchasesException(
                    content, 'The receipt could not be authorized')
        elif status != 0:
            raise RetryReceiptValidation(
                content, 'Unknown status code %s. Retry' % status)

        if 'receipt' not in content:
            raise ReceiptValidationException(
                content, 'Unable to get receipt from Apple')

        receipt = content.get('receipt', [])
        if not len(receipt):
            raise ReceiptValidationException(content, 'Not enough receipt!')

        in_app_purchases = receipt.get('in_app', [])
        if not len(in_app_purchases):
            raise NoPurchasesException(content, 'No IAPs for receipt!')

        latest_receipt = receipt.get('latest_receipt')
        if latest_receipt:
            try:
                receipt['latest_receipt_encoded'] = latest_receipt
                receipt['latest_receipt'] = base64.b64decode(latest_receipt)
            except TypeError:
                raise ReceiptValidationException(
                    content, 'Cannot decode latest_receipt')

        return content

def validate_device(decoded_receipt, bundle_ids):
    if 'bundle_id' not in decoded_receipt:
        raise InvalidReceipt(u'Unknown decoded receipt format!')
    if decoded_receipt['bundle_id'] not in bundle_ids:
        raise InvalidReceipt(
            u'Unexpected bundle_id in decoded receipt {}'.format(
                decoded_receipt['bundle_id']))
    return True

def validate_product(decoded_receipt, product_ids):
    # If there are no products in the receipt, they are all ok
    for in_app in decoded_receipt.get('in_app', []):
        if 'product_id' not in in_app:
            raise InvalidReceipt(u'Unknown decoded receipt format!')
        if in_app['product_id'] not in product_ids:
            raise InvalidReceipt(
                u'Unexpected product_id in decoded receipt {}'.format(
                    in_app['product_id']))
    return True

def validate_production_receipt(decoded_receipt):
    validate_device(decoded_receipt, [PRODUCTION_BUNDLE_ID])
    validate_product(decoded_receipt, PRODUCTION_PRODUCT_IDS)

def validate_debug_receipt(decoded_receipt):
    if not decoded_receipt.get('_sandbox', False):
        raise InvalidReceipt('Debug receipts must be in the sandbox!')

    bundle_ids = ([DEBUG_BUNDLE_ID, PRODUCTION_BUNDLE_ID]
                  if DEBUG_BUNDLE_ID else [PRODUCTION_BUNDLE_ID])

    # The sandbox can have both production and debug bundle and product ids.
    # This is because when the app is in review, they test on the sandbox,
    # but are using a production build of the app.
    validate_device(decoded_receipt, bundle_ids)
    validate_product(
        decoded_receipt, DEBUG_PRODUCT_IDS | PRODUCTION_PRODUCT_IDS)

def validate_receipt_is_active(data, timedelta, is_test=False):
    # Establish grace period
    delta_kwargs = {'minutes': 1} if is_test else {'days': 1}
    grace_period = datetime.timedelta(**delta_kwargs)

    # Check with Apple
    try:
        updated_content = validate_receipt_with_apple(data)
    except RetryReceiptValidation:
        # Try one more time
        updated_content = validate_receipt_with_apple(data)

    # Validate the device and product are ok
    local_validation = (
        validate_debug_receipt if is_test else validate_production_receipt)
    local_validation(updated_content['receipt'])

    # Use the latest receipt information from Apple, otherwise
    # use the IAP from the receipt. latest_receipt_info is only returned for
    # iOS 6 style transaction receipts for auto-renewable subscriptions.
    # From Apple:
    #
    # The values of the latest_receipt and latest_receipt_info keys are useful
    # when checking whether an auto-renewable subscription is currently active.
    # By providing any transaction receipt for the subscription and checking
    # these values, you can get information about the currently-active
    # subscription period. If the receipt being validated is for the latest
    # renewal, the value for latest_receipt is the same as receipt-data
    # (in the request) and the value for latest_receipt_info is the same as
    # receipt.
    iaps = updated_content.get(
        'latest_receipt_info', updated_content['receipt']['in_app'])

    # Ensure the updated receipt has an active subscription.
    for iap in iaps:
        if iap.get('cancellation_date'):
            # This iap is canceled. Ignore it
            continue
        # Look for an expires_date
        expires_date_ms = int(iap.get('expires_date_ms', 0))
        if expires_date_ms:
            # See if this iap is expired
            expires_date_sec = expires_date_ms / 1000.0
            expires_date = datetime.datetime.utcfromtimestamp(expires_date_sec)
            if datetime.datetime.utcnow() < expires_date + grace_period:
                return
        else:
            # Check the subscription period
            purchase_date_sec = int(iap['original_purchase_date_ms']) / 1000.0
            purchase_date = datetime.datetime.utcfromtimestamp(
                purchase_date_sec)
            expires_date = purchase_date + timedelta
            if datetime.datetime.utcnow() < expires_date + grace_period:
                return
    raise NoActiveReceiptException(
        updated_content, 'No active IAP was found in the receipt')
<commit_msg>Allow filtering by same product_ids for IAPs<commit_after>import base64
import datetime
import json
import os
import subprocess

import requests
from Crypto.Util import asn1
from django.conf import settings

try:
    from simplejson.decoder import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError

__all__ = [
    'InvalidReceipt',
    'NoActiveReceiptException',
    'NoPurchasesException',
    'ReceiptValidationException',
    'parse_receipt',
    'validate_debug_receipt',
    'validate_production_receipt',
    'validate_receipt_with_apple',
    'validate_receipt_is_active',
]

CA_FILE = settings.IAP_SETTINGS['CA_FILE']

IAP_SHARED_SECRET = settings.IAP_SETTINGS.get('SHARED_SECRET')

PRODUCTION_VERIFICATION_URL = 'https://buy.itunes.apple.com/verifyReceipt'
SANDBOX_VERIFICATION_URL = 'https://sandbox.itunes.apple.com/verifyReceipt'

PRODUCTION_BUNDLE_ID = settings.IAP_SETTINGS['PRODUCTION_BUNDLE_ID']
DEBUG_BUNDLE_ID = settings.IAP_SETTINGS.get('DEBUG_BUNDLE_ID')

PRODUCTION_PRODUCT_IDS = settings.IAP_SETTINGS.get(
    'PRODUCTION_PRODUCT_IDS', set())
DEBUG_PRODUCT_IDS = settings.IAP_SETTINGS.get(
    'DEBUG_PRODUCT_IDS', set())

class InvalidReceipt(Exception):
    pass

class ReceiptValidationException(Exception):
    def __init__(self, receipt, *args, **kwargs):
        self.receipt = receipt
        super(ReceiptValidationException, self).__init__(*args, **kwargs)

class RetryReceiptValidation(ReceiptValidationException):
    pass

class NoActiveReceiptException(ReceiptValidationException):
    pass

class NoPurchasesException(ReceiptValidationException):
    pass

def verify_receipt_sig(raw_data):
    proc = subprocess.Popen([
        'openssl', 'smime', '-verify',
        '-inform', 'der',
        '-CAfile', CA_FILE,
        '-binary',
    ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    signed_data, err_data = proc.communicate(raw_data)
    if proc.wait() != os.EX_OK:
        raise InvalidReceipt('Signature verification failed:\n' + err_data)
    return signed_data

def decode_obj(data, expected_type):
    der = asn1.DerObject()
    der.decode(data)
    if der.typeTag != expected_type:
        raise InvalidReceipt('Expected tag type {}; got {}'.format(
            expected_type, der.typeTag))
    return der.payload

def decode_seq_set(data):
    result = []
    offset = 0
    while offset < len(data):
        der = asn1.DerSequence()
        length = der.decode(data[offset:])
        offset += length
        result.append(list(der))
    return result

def decode_ia5(data):
    return decode_obj(data, 22).decode('ascii')

def decode_utf8(data):
    return decode_obj(data, 12).decode('utf-8')

def decode_int(data):
    der = asn1.DerInteger()
    der.decode(data)
    return der.value

def decode_receipt(data):
    # See https://developer.apple.com/library/ios/releasenotes/General/
    #   ValidateAppStoreReceipt/Chapters/ReceiptFields.html
    fields = {
        0: ('_environment', decode_utf8),
        2: ('bundle_id', decode_utf8),
        3: ('application_version', decode_utf8),
        # 4: ('_opaque_value', lambda x: x),
        # 5: ('_sha1_hash', lambda x: x),
        17: ('in_app', decode_receipt),
        19: ('original_application_version', decode_utf8),
        21: ('expiration_date', decode_ia5),
        1701: ('quantity', decode_int),
        1702: ('product_id', decode_utf8),
        1703: ('transaction_id', decode_utf8),
        1704: ('purchase_date', decode_ia5),
        1705: ('original_transaction_id', decode_utf8),
        1706: ('original_purchase_date', decode_ia5),
        1708: ('expires_date', decode_ia5),
        1711: ('web_order_line_item_id', decode_int),
        1712: ('cancellation_date', decode_ia5),
    }
    list_fields = [17]

    payload = decode_obj(data, 49)
    result = {}
    for attr_type, attr_version, attr_raw_value in decode_seq_set(payload):
        attr_value = decode_obj(attr_raw_value, 4)

        name, decoder = fields.get(attr_type, (None, None))
        if attr_type in list_fields:
            result.setdefault(name, []).append(decoder(attr_value))
        elif name is not None:
            result[name] = decoder(attr_value)
        # else:
        #    result['_unknown_{}'.format(attr_type)] = attr_value

    result['_sandbox'] = (
        result.get('original_application_version') == '1.0' and
        result.get('_environment') != 'Production')

    return result

def parse_receipt(raw_data):
    return decode_receipt(verify_receipt_sig(raw_data))

def validate_receipt_with_apple(data):
    payload = {'receipt-data': base64.b64encode(data)}

    if IAP_SHARED_SECRET:
        payload['password'] = IAP_SHARED_SECRET

    # Docs at http://goo.gl/WV5U63
    for url in (PRODUCTION_VERIFICATION_URL, SANDBOX_VERIFICATION_URL):
        r = requests.post(url, data=json.dumps(payload))
        r.raise_for_status()
        try:
            content = r.json()
        except JSONDecodeError:
            raise ReceiptValidationException({}, 'Unable to read response')

        if 'status' not in content:
            raise ReceiptValidationException(content, 'Unknown response format')
        status = content.get('status', 21000)
        if status == 21000:
            # The App Store could not read the JSON object you provided.
            raise ReceiptValidationException(content, 'Unable to read payload')
        elif status == 21002:
            # The data in the receipt-data property was malformed or missing.
            raise ReceiptValidationException(content, 'Malformed receipt-data')
        elif status == 21003:
            # The receipt could not be authenticated.
            raise ReceiptValidationException(
                content, 'Receipt is from an unknown source')
        elif status == 21004:
            # Bad shared secret for the app / auth failed
            # NOTE: Only returned for iOS 6 style transaction receipts for
            # auto-renewable subscriptions.
            raise ReceiptValidationException(
                content, 'The shared secret does not match one on file')
        elif status == 21005:
            # The receipt server is not currently available.
            raise ReceiptValidationException(content, 'WebObjects')
        elif status == 21006:
            # The receipt is inactive
            # NOTE: Only returned for iOS 6 style transaction receipts for
            # auto-renewable subscriptions. For iOS 7 style app receipts, the
            # status code is reflects the status of the app receipt as a whole.
            # For example, if you send a valid app receipt that contains an
            # expired subscription, the response is 0 because the receipt as a
            # whole is valid.
            raise ReceiptValidationException(content, 'Inactive subscription')
        elif status == 21007:
            if url == PRODUCTION_VERIFICATION_URL:
                # We need to try the other environment
                continue
            raise ReceiptValidationException(content, 'Cannot try another url!')
        elif status == 21008:
            raise ReceiptValidationException(content, 'We already tried prod!')
        elif status == 21009:
            # This seems to be an internal Apple error. In this case,
            # one should retry the request
            raise RetryReceiptValidation(content, 'Internal Apple error. Retry')
        elif status == 21010:
            # This receipt could not be authorized. Treated as if the purchase
            # was never made.
            raise NoPurchasesException(
                    content, 'The receipt could not be authorized')
        elif status != 0:
            raise RetryReceiptValidation(
                content, 'Unknown status code %s. Retry' % status)

        if 'receipt' not in content:
            raise ReceiptValidationException(
                content, 'Unable to get receipt from Apple')

        receipt = content.get('receipt', [])
        if not len(receipt):
            raise ReceiptValidationException(content, 'Not enough receipt!')

        in_app_purchases = receipt.get('in_app', [])
        if not len(in_app_purchases):
            raise NoPurchasesException(content, 'No IAPs for receipt!')

        latest_receipt = receipt.get('latest_receipt')
        if latest_receipt:
            try:
                receipt['latest_receipt_encoded'] = latest_receipt
                receipt['latest_receipt'] = base64.b64decode(latest_receipt)
            except TypeError:
                raise ReceiptValidationException(
                    content, 'Cannot decode latest_receipt')

        return content

def validate_device(decoded_receipt, bundle_ids):
    if 'bundle_id' not in decoded_receipt:
        raise InvalidReceipt(u'Unknown decoded receipt format!')
    if decoded_receipt['bundle_id'] not in bundle_ids:
        raise InvalidReceipt(
            u'Unexpected bundle_id in decoded receipt {}'.format(
                decoded_receipt['bundle_id']))
    return True

def validate_product(decoded_receipt, product_ids):
    # If there are no products in the receipt, they are all ok
    for in_app in decoded_receipt.get('in_app', []):
        if 'product_id' not in in_app:
            raise InvalidReceipt(u'Unknown decoded receipt format!')
        if in_app['product_id'] not in product_ids:
            raise InvalidReceipt(
                u'Unexpected product_id in decoded receipt {}'.format(
                    in_app['product_id']))
    return True

def validate_production_receipt(decoded_receipt):
    validate_device(decoded_receipt, [PRODUCTION_BUNDLE_ID])
    validate_product(decoded_receipt, PRODUCTION_PRODUCT_IDS)

def validate_debug_receipt(decoded_receipt):
    if not decoded_receipt.get('_sandbox', False):
        raise InvalidReceipt('Debug receipts must be in the sandbox!')

    bundle_ids = ([DEBUG_BUNDLE_ID, PRODUCTION_BUNDLE_ID]
                  if DEBUG_BUNDLE_ID else [PRODUCTION_BUNDLE_ID])

    # The sandbox can have both production and debug bundle and product ids.
    # This is because when the app is in review, they test on the sandbox,
    # but are using a production build of the app.
    validate_device(decoded_receipt, bundle_ids)
    validate_product(
        decoded_receipt, DEBUG_PRODUCT_IDS | PRODUCTION_PRODUCT_IDS)

def validate_receipt_is_active(data, timedelta, is_test=False, product_id=None):
    # Establish grace period
    delta_kwargs = {'minutes': 1} if is_test else {'days': 1}
    grace_period = datetime.timedelta(**delta_kwargs)

    # Check with Apple
    try:
        updated_content = validate_receipt_with_apple(data)
    except RetryReceiptValidation:
        # Try one more time
        updated_content = validate_receipt_with_apple(data)

    # Validate the device and product are ok
    local_validation = (
        validate_debug_receipt if is_test else validate_production_receipt)
    local_validation(updated_content['receipt'])

    # Use the latest receipt information from Apple, otherwise
    # use the IAP from the receipt. latest_receipt_info is only returned for
    # iOS 6 style transaction receipts for auto-renewable subscriptions.
    # From Apple:
    #
    # The values of the latest_receipt and latest_receipt_info keys are useful
    # when checking whether an auto-renewable subscription is currently active.
    # By providing any transaction receipt for the subscription and checking
    # these values, you can get information about the currently-active
    # subscription period. If the receipt being validated is for the latest
    # renewal, the value for latest_receipt is the same as receipt-data
    # (in the request) and the value for latest_receipt_info is the same as
    # receipt.
    iaps = updated_content.get(
        'latest_receipt_info', updated_content['receipt']['in_app'])

    # Ensure the updated receipt has an active subscription.
    for iap in iaps:
        if iap.get('cancellation_date'):
            # This iap is canceled. Ignore it
            continue

        # If we were given a product_id, make sure this iap is for that same
        # product_id
        if product_id is not None and iap.get('product_id') != product_id:
            # Ignore this IAP as it is for a different product
            continue

        # Look for an expires_date
        expires_date_ms = int(iap.get('expires_date_ms', 0))
        if expires_date_ms:
            # See if this iap is expired
            expires_date_sec = expires_date_ms / 1000.0
            expires_date = datetime.datetime.utcfromtimestamp(expires_date_sec)
            if datetime.datetime.utcnow() < expires_date + grace_period:
                return
        else:
            # Check the subscription period
            purchase_date_sec = int(iap['original_purchase_date_ms']) / 1000.0
            purchase_date = datetime.datetime.utcfromtimestamp(
                purchase_date_sec)
            expires_date = purchase_date + timedelta
            if datetime.datetime.utcnow() < expires_date + grace_period:
                return
    raise NoActiveReceiptException(
        updated_content, 'No active IAP was found in the receipt')
<|endoftext|>