<commit_before># from  samuel . bucquet Tue, 13 Sep 2016 05:49:33 -0700
# https://groups.google.com/forum/#!msg/beagleboard/u9We3y-yrZc/z1dKE4F-BQAJ
#
# As I encountered the same behaviour as yours when switching to kernel 4.4,
# I resolved mine with the cape_universal (the default with the official
# image provided) with a line 'config-pin P9_14 pwm' for the PWM1A for ex.
# But, the numbering of the PWMs differ at each booting of the bbb ! not very
# easy to handle, so after I saw the PWM with npwm == 2 are not the ECAP ones

# https://www.kernel.org/doc/Documentation/pwm.txt

import os
import glob

class Pwm(object):
    def __init__(self, channelName):
        self.channels = {}
        self.channel_name = channelName
        self.channel = {}
        self.opened = False

        self.enabled = False
        self.inverted = False
        self.period = 10000000
        self.active_time = 0

    def __enter__(self):
        self.scan()
        self.open(self.channel_name)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.is_enabled = False
        self.close()

    def scan(self):
        ecap_pwms = ['ECAPPWM0', 'ECAPPWM2']
        ehr_pwms = ['EHRPWM0A', 'EHRPWM0B', 'EHRPWM1A', 'EHRPWM1B', 'EHRPWM2A', 'EHRPWM2B']
        path = '/sys/class/pwm'
        chips = glob.glob('{}/pwmchip*'.format(path))
        ecap_index = 0
        ehr_index = 0
        for chip in chips:
            print 'looking in: ' + '{}/npwm'.format(chip)
            npwm = int(open('{}/npwm'.format(chip)).read())
            if npwm == 1:
                if ecap_index == 0:
                    i = 0
                    chan_name = ecap_pwms[ecap_index]
                    self.channels[chan_name] = {'chip' : chip, 'port' : '{}'.format(0)}
                    chan = self.channels[chan_name]
                    print ' (assigning {}/pwm{} to {})'.format(chan['chip'], chan['port'], chan_name)
                elif ecap_index == 1:
                    i = 2
                    chan_name = ecap_pwms[ecap_index]
                    self.channels[chan_name] = {'chip' : chip, 'port' : '{}'.format(0)}
                    chan = self.channels[chan_name]
                    print ' (assigning {}/pwm{} to {})'.format(chan['chip'], chan['port'], chan_name)
                else:
                    pass
                ecap_index += 1
                print
            elif npwm == 2:
                for i in [0, 1]:
                    chan_name = ehr_pwms[ehr_index+i]
                    self.channels[chan_name] = {'chip' : chip, 'port' : '{}'.format(i)}
                    chan = self.channels[chan_name]
                    print ' (assigning {}/pwm{} to {})'.format(chan['chip'], chan['port'], chan_name)
                ehr_index += 2
                print
            else:
                print '(unrecognized chip npwm count)'

    def open(self, name):
        self.channel = self.channels[name]
        print '{}/export'.format(self.channel['chip']), self.channel['port']
        open('{}/export'.format(self.channel['chip']), 'w').write(self.channel['port'])
        self.opened = True

    def close(self):
        if self.opened:
            print '{}/unexport'.format(self.channel['chip']), self.channel['port']
            open('{}/unexport'.format(self.channel['chip']), 'w').write(self.channel['port'])
        self.opened = False

    def write(self, channel, command, value):
        #print '{}/pwm{}/{}'.format(self.channel['chip'], self.channel['port'], command), value
        open('{}/pwm{}/{}'.format(self.channel['chip'], self.channel['port'], command), 'w').write(value)

    @property
    def is_enabled(self):
        return self.enabled

    @is_enabled.setter
    def is_enabled(self, boolean):
        self.enabled = boolean
        if self.enabled == True:
            state = '1'
        else:
            state = '0'
        self.write(self.channel, 'enable', state)

    @property
    def is_inverted(self):
        return self.inverted

    @is_inverted.setter
    def is_inverted(self, boolean):
        self.inverted = boolean
        if self.inverted == True:
            state = 'inversed'
        else:
            state = 'normal'
        self.write(self.channel, 'polarity', state)

    @property
    def frequency(self):
        return 1/self.period

    @frequency.setter
    def frequency(self, hertz):
        if hertz < 1:
            hertz = 1
        elif hertz > 10e6:
            hertz = 10e6

        self.period = 1.0/float(hertz)
        val = '{}'.format(int(self.period * 1e9))
        self.write(self.channel, 'period', val)

    @property
    def dutycycle(self):
        return self.active_time / self.period * 100

    @dutycycle.setter
    def dutycycle(self, percent):
        if percent < 0.0:
            percent = 0
        elif percent > 100.0:
            percent = 100.0

        nanoseconds = int(self.period * percent/100 * 1e9)
        self.active_time = nanoseconds
        val = '{}'.format(nanoseconds)
        self.write(self.channel, 'duty_cycle', val)

if __name__ == '__main__':
    import time

    with Pwm('ECAPPWM0') as pwm:
        pwm.is_inverted = False
        pwm.dutycycle = 0
        pwm.frequency = 1000
        pwm.is_enabled = True
        print pwm.enabled

        pwm.dutycycle = 25.0
        time.sleep(2)
        pwm.dutycycle = 50.0
        time.sleep(2)
        pwm.dutycycle = 75.0
        time.sleep(2)
        pwm.dutycycle = 95.0
        time.sleep(2)

        pwm.is_enabled = False

<commit_msg>hide debug prints<commit_after># from  samuel . bucquet Tue, 13 Sep 2016 05:49:33 -0700
# https://groups.google.com/forum/#!msg/beagleboard/u9We3y-yrZc/z1dKE4F-BQAJ
#
# As I encountered the same behaviour as yours when switching to kernel 4.4,
# I resolved mine with the cape_universal (the default with the official
# image provided) with a line 'config-pin P9_14 pwm' for the PWM1A for ex.
# But, the numbering of the PWMs differ at each booting of the bbb ! not very
# easy to handle, so after I saw the PWM with npwm == 2 are not the ECAP ones

# https://www.kernel.org/doc/Documentation/pwm.txt

import os
import glob

class Pwm(object):
    def __init__(self, channelName):
        self.channels = {}
        self.channel_name = channelName
        self.channel = {}
        self.opened = False

        self.enabled = False
        self.inverted = False
        self.period = 10000000
        self.active_time = 0

    def __enter__(self):
        self.scan()
        self.open(self.channel_name)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.is_enabled = False
        self.close()

    def scan(self):
        ecap_pwms = ['ECAPPWM0', 'ECAPPWM2']
        ehr_pwms = ['EHRPWM0A', 'EHRPWM0B', 'EHRPWM1A', 'EHRPWM1B', 'EHRPWM2A', 'EHRPWM2B']
        path = '/sys/class/pwm'
        chips = glob.glob('{}/pwmchip*'.format(path))
        ecap_index = 0
        ehr_index = 0
        for chip in chips:
            #print 'looking in: ' + '{}/npwm'.format(chip)
            npwm = int(open('{}/npwm'.format(chip)).read())
            if npwm == 1:
                if ecap_index == 0:
                    i = 0
                    chan_name = ecap_pwms[ecap_index]
                    self.channels[chan_name] = {'chip' : chip, 'port' : '{}'.format(0)}
                    chan = self.channels[chan_name]
                    #print ' (assigning {}/pwm{} to {})'.format(chan['chip'], chan['port'], chan_name)
                elif ecap_index == 1:
                    i = 2
                    chan_name = ecap_pwms[ecap_index]
                    self.channels[chan_name] = {'chip' : chip, 'port' : '{}'.format(0)}
                    chan = self.channels[chan_name]
                    #print ' (assigning {}/pwm{} to {})'.format(chan['chip'], chan['port'], chan_name)
                else:
                    pass
                ecap_index += 1
                #print
            elif npwm == 2:
                for i in [0, 1]:
                    chan_name = ehr_pwms[ehr_index+i]
                    self.channels[chan_name] = {'chip' : chip, 'port' : '{}'.format(i)}
                    chan = self.channels[chan_name]
                    #print ' (assigning {}/pwm{} to {})'.format(chan['chip'], chan['port'], chan_name)
                ehr_index += 2
                #print
            else:
                print '(unrecognized chip npwm count)'

    def open(self, name):
        self.channel = self.channels[name]
        #print '{}/export'.format(self.channel['chip']), self.channel['port']
        open('{}/export'.format(self.channel['chip']), 'w').write(self.channel['port'])
        self.opened = True

    def close(self):
        if self.opened:
            #print '{}/unexport'.format(self.channel['chip']), self.channel['port']
            open('{}/unexport'.format(self.channel['chip']), 'w').write(self.channel['port'])
        self.opened = False

    def write(self, channel, command, value):
        #print '{}/pwm{}/{}'.format(self.channel['chip'], self.channel['port'], command), value
        open('{}/pwm{}/{}'.format(self.channel['chip'], self.channel['port'], command), 'w').write(value)

    @property
    def is_enabled(self):
        return self.enabled

    @is_enabled.setter
    def is_enabled(self, boolean):
        self.enabled = boolean
        if self.enabled == True:
            state = '1'
        else:
            state = '0'
        self.write(self.channel, 'enable', state)

    @property
    def is_inverted(self):
        return self.inverted

    @is_inverted.setter
    def is_inverted(self, boolean):
        self.inverted = boolean
        if self.inverted == True:
            state = 'inversed'
        else:
            state = 'normal'
        self.write(self.channel, 'polarity', state)

    @property
    def frequency(self):
        return 1/self.period

    @frequency.setter
    def frequency(self, hertz):
        if hertz < 1:
            hertz = 1
        elif hertz > 10e6:
            hertz = 10e6

        self.period = 1.0/float(hertz)
        val = '{}'.format(int(self.period * 1e9))
        self.write(self.channel, 'period', val)

    @property
    def dutycycle(self):
        return self.active_time / self.period * 100

    @dutycycle.setter
    def dutycycle(self, percent):
        if percent < 0.0:
            percent = 0
        elif percent > 100.0:
            percent = 100.0

        nanoseconds = int(self.period * percent/100 * 1e9)
        self.active_time = nanoseconds
        val = '{}'.format(nanoseconds)
        self.write(self.channel, 'duty_cycle', val)

if __name__ == '__main__':
    import time

    with Pwm('ECAPPWM0') as pwm:
        pwm.is_inverted = False
        pwm.dutycycle = 0
        pwm.frequency = 1000
        pwm.is_enabled = True
        print 'pwm enabled = {}'.format(pwm.enabled)

        pwm.dutycycle = 25.0
        time.sleep(2)
        pwm.dutycycle = 50.0
        time.sleep(2)
        pwm.dutycycle = 75.0
        time.sleep(2)
        pwm.dutycycle = 95.0
        time.sleep(2)

        pwm.is_enabled = False

<|endoftext|>


<commit_before>from lxml import etree

from odoo.exceptions import ValidationError
from odoo import models, fields, api, _
from odoo.addons import decimal_precision as dp
from mako.template import Template
from mako.runtime import Context
from odoo.tools.safe_eval import safe_eval
from io import StringIO
import logging

_logger = logging.getLogger(__name__)

class ProductTemplate(models.Model):
    _inherit = 'product.template'

    @api.one
    @api.depends('product_variant_ids.product_tmpl_id')
    def _compute_product_variant_count(self):
        """For configurable products return the number of variants configured or 1
        as many views and methods trigger only when a template has at least one
        variant attached. Since we create them from the template we should have
        access to them always"""
        super(ProductTemplate, self)._compute_product_variant_count()
        if self.config_ok and not self.product_variant_count:
            self.product_variant_count = 1

    @api.multi
    @api.depends('attribute_line_ids.value_ids')
    def _compute_template_attr_vals(self):
        for product_tmpl in self.filtered(lambda t: t.config_ok):
            value_ids = product_tmpl.attribute_line_ids.mapped('value_ids')
            product_tmpl.attribute_line_val_ids = value_ids

    @api.multi
    @api.constrains('attribute_line_ids', 'attribute_value_line_ids')
    def check_attr_value_ids(self):
        for product_tmpl in self:
            attr_val_lines = product_tmpl.attribute_value_line_ids
            attr_val_ids = attr_val_lines.mapped('value_ids')
            if not attr_val_ids <= product_tmpl.attribute_line_val_ids:
                raise ValidationError(
                    _("All attribute values used in attribute value lines "
                      "must be defined in the attribute lines of the template")
                )

    @api.multi
    @api.constrains('attribute_value_line_ids')
    def _validate_unique_config(self):
        """Check for duplicate configurations for the same attribute value"""
        attr_val_line_vals = self.attribute_value_line_ids.read(
            ['value_id', 'value_ids'], load=False
        )
        attr_val_line_vals = [
            (l['value_id'], tuple(l['value_ids'])) for l in attr_val_line_vals
        ]
        if len(set(attr_val_line_vals)) != len(attr_val_line_vals):
            raise ValidationError(
                _('You cannot have a duplicate configuration for the '
                  'same value')
            )

    config_ok = fields.Boolean(string='Can be Configured')

    config_line_ids = fields.One2many(
        comodel_name='product.config.line',
        inverse_name='product_tmpl_id',
        string="Attribute Dependencies"
    )

    config_image_ids = fields.One2many(
        comodel_name='product.config.image',
        inverse_name='product_tmpl_id',
        string='Configuration Images'
    )

    attribute_value_line_ids = fields.One2many(
        comodel_name='product.attribute.value.line',
        inverse_name='product_tmpl_id',
        string="Attribute Value Lines"
    )

    attribute_line_val_ids = fields.Many2many(
        comodel_name='product.attribute.value',
        compute='_compute_template_attr_vals',
        store=False
    )

    config_step_line_ids = fields.One2many(
        comodel_name='product.config.step.line',
        inverse_name='product_tmpl_id',
        string='Configuration Lines'
    )

    mako_tmpl_name = fields.Text(
        string='Variant name',
        help="Generate Name based on Mako Template"
    )

    @api.multi
    def get_product_attribute_values_action(self):
        self.ensure_one()
        action = self.env.ref(
            'product.product_attribute_value_action').read()[0]
        value_ids = self.attribute_line_ids.mapped('value_ids').ids
        action['domain'] = [('id', 'in', value_ids)]
        context = safe_eval(action['context'], {'active_id': self.id})
        context.update({'active_id': self.id})
        action['context'] = context
        return action

    @api.multi
    @api.constrains('attribute_line_ids')
    def _check_default_values(self):
        """Validate default values set on the product template"""
        default_val_ids = self.attribute_line_ids.filtered(
            lambda l: l.default_val).mapped('default_val').ids

        # TODO: Remove if cond when PR with raise error on github is merged
        cfg_session_obj = self.env['product.config.session']
        valid_conf = cfg_session_obj.validate_configuration(
            value_ids=default_val_ids, final=False
        )
        if not valid_conf:
            raise ValidationError(
                _('Default values provided generate an invalid configuration')
            )

    @api.multi
    @api.constrains('config_line_ids')
    def _check_default_value_domains(self):
        try:
            self._check_default_values()
        except ValidationError:
            raise ValidationError(
                _('Restrictions added make the current default values '
                  'generate an invalid configuration')
            )

    @api.multi
    def toggle_config(self):
        for record in self:
            record.config_ok = not record.config_ok

    @api.multi
    def create_variant_ids(self):
        """ Prevent configurable products from creating variants as these serve
            only as a template for the product configurator"""
        templates = self.filtered(lambda t: not t.config_ok)
        if not templates:
            return None
        return super(ProductTemplate, templates).create_variant_ids()

    @api.multi
    def unlink(self):
        """ Prevent the removal of configurable product templates
            from variants"""
        for template in self:
            variant_unlink = self.env.context.get('unlink_from_variant', False)
            if template.config_ok and variant_unlink:
                self -= template
        res = super(ProductTemplate, self).unlink()
        return res

    @api.multi
    def copy(self, default=None):
        res = super(ProductTemplate, self).copy(default=default)
        return res

    @api.multi
    def configure_product(self):
        return self.create_config_wizard()

    def create_config_wizard(self, model_name="product.configurator",
                             extra_vals=None, click_next=True):
        """create product configuration wizard
        - return action to launch wizard
        - click on next step based on value of click_next"""

        action = {
            'type': 'ir.actions.act_window',
            'res_model': 'product.configurator',
            'name': "Product Configurator",
            'view_mode': 'form',
            'target': 'new',
            'context': dict(
                self.env.context,
                wizard_model='product.configurator',
            ),
        }

        wizard_obj = self.env[model_name]
        wizard_vals = {
            'product_tmpl_id': self.id
        }
        if extra_vals:
            wizard_vals.update(extra_vals)
        wizard = wizard_obj.create(wizard_vals)
        if click_next:
            action = wizard.action_next_step()
        return action

class ProductProduct(models.Model):
    _inherit = 'product.product'
    _rec_name = 'config_name'

    def _get_conversions_dict(self):
        conversions = {
            'float': float,
            'int': int
        }
        return conversions

    @api.constrains('attribute_value_ids')
    def _check_duplicate_product(self):
        if not self.config_ok:
            return None

        # At the moment, I don't have enough confidence with my understanding
        # of binary attributes, so will leave these as not matching...
        # In theory, they should just work, if they are set to "non search"
        # in custom field def!
        # TODO: Check the logic with binary attributes
        if not self.value_custom_ids.filtered(lambda cv: cv.attachment_ids):
            config_session_obj = self.env['product.config.session']
            custom_vals = {
                cv.attribute_id.id: cv.value
                for cv in self.value_custom_ids
            }
            duplicates = config_session_obj.search_variant(
                product_tmpl_id=self.product_tmpl_id.id,
                value_ids=self.attribute_value_ids.ids,
                custom_vals=custom_vals
            ).filtered(lambda p: p.id != self.id)

            if duplicates:
                raise ValidationError(
                    _("Configurable Products cannot have duplicates "
                      "(identical attribute values)")
                )

    @api.model
    def _get_config_name(self):
        return self.name

    @api.model
    def _get_mako_context(self, buf):
        return Context(
            buf, product=self,
            attribute_values=self.attribute_value_ids,
            steps=self.product_tmpl_id.config_step_line_ids,
            template=self.product_tmpl_id)

    @api.model
    def _get_mako_tmpl_name(self):
        if self.mako_tmpl_name:
            try:
                mytemplate = Template(self.mako_tmpl_name or '')
                buf = StringIO()
                ctx = self._get_mako_context(buf)
                mytemplate.render_context(ctx)
                return buf.getvalue()
            except Exception:
                _logger.error(
                    _("Error while calculating mako product name: %s") %
                    self.display_name)
        return self.display_name

    @api.depends('attribute_value_ids.price_ids.weight_extra',
                 'attribute_value_ids.price_ids.product_tmpl_id')
    def _compute_product_weight_extra(self):
        for product in self:
            weight_extra = 0.0
            attr_prices = product.mapped('attribute_value_ids.price_ids')
            for attribute_price in attr_prices:
                if attribute_price.product_tmpl_id == product.product_tmpl_id:
                    weight_extra += attribute_price.weight_extra
            product.weight_extra = weight_extra

    @api.depends('product_tmpl_id.weight', 'weight_extra')
    def _compute_product_weight(self):
        for product in self:
            tmpl_weight = product.product_tmpl_id.weight
            product.weight = tmpl_weight + product.weight_extra

    config_name = fields.Char(
        string="Name",
        size=256,
        compute='_compute_name',
    )
    value_custom_ids = fields.One2many(
        comodel_name='product.attribute.value.custom',
        inverse_name='product_id',
        string='Custom Values',
        readonly=True
    )
    price_extra = fields.Float(
        compute='_compute_product_price_extra',
        string='Variant Extra Price',
        help="This is the sum of the extra price of all attributes",
        digits=dp.get_precision('Product Price')
    )
    weight_extra = fields.Float(
        string='Weight Extra',
        compute='_compute_product_weight_extra'
    )

    weight = fields.Float(compute='_compute_product_weight')

    @api.multi
    def get_product_attribute_values_action(self):
        self.ensure_one()
        action = self.env.ref(
            'product.product_attribute_value_action').read()[0]
        value_ids = self.attribute_value_ids.ids
        action['domain'] = [('id', 'in', value_ids)]
        context = safe_eval(
            action['context'],
            {'active_id': self.product_tmpl_id.id}
        )
        context.update({'active_id': self.product_tmpl_id.id})
        action['context'] = context
        return action

    @api.multi
    def _check_attribute_value_ids(self):
        """ Removing multi contraint attribute to enable multi selection. """
        return True

    _constraints = [
        (_check_attribute_value_ids, None, ['attribute_value_ids'])
    ]

    @api.model
    def fields_view_get(self, view_id=None, view_type='form',
                        toolbar=False, submenu=False):
        """ For configurable products switch the name field with the config_name
            so as to keep the view intact in whatever form it is at the moment
            of execution and not duplicate the original just for the sole
            purpose of displaying the proper name"""
        res = super(ProductProduct, self).fields_view_get(
            view_id=view_id, view_type=view_type,
            toolbar=toolbar, submenu=submenu
        )
        if self.env.context.get('default_config_ok'):
            xml_view = etree.fromstring(res['arch'])
            xml_name = xml_view.xpath("//field[@name='name']")
            xml_label = xml_view.xpath("//label[@for='name']")
            if xml_name:
                xml_name[0].attrib['name'] = 'config_name'
                if xml_label:
                    xml_label[0].attrib['for'] = 'config_name'
                view_obj = self.env['ir.ui.view']
                xarch, xfields = view_obj.postprocess_and_fields(self._name,
                                                                 xml_view,
                                                                 view_id)
                res['arch'] = xarch
                res['fields'] = xfields
        return res

    @api.multi
    def unlink(self):
        """ Signal unlink from product variant through context so
            removal can be stopped for configurable templates """
        ctx = dict(self.env.context, unlink_from_variant=True)
        self.env.context = ctx
        return super(ProductProduct, self).unlink()

    @api.multi
    def _compute_name(self):
        """ Compute the name of the configurable products and use template
            name for others"""
        for product in self:
            if product.config_ok:
                product.config_name = product._get_config_name()
            else:
                product.config_name = product.name

    @api.multi
    def reconfigure_product(self):
        """ Creates and launches a product configurator wizard with a linked
        template and variant in order to re-configure an existing product.
        It is essentially a shortcut to pre-fill configuration
        data of a variant"""

        extra_vals = {
            'product_id': self.id,
        }
        return self.product_tmpl_id.create_config_wizard(
            extra_vals=extra_vals)
<commit_msg>[FIX] Computing PIL image only on final step<commit_after>from lxml import etree

from odoo.exceptions import ValidationError
from odoo import models, fields, api, _
from odoo.addons import decimal_precision as dp
from mako.template import Template
from mako.runtime import Context
from odoo.tools.safe_eval import safe_eval
from io import StringIO
import logging

_logger = logging.getLogger(__name__)

class ProductTemplate(models.Model):
    _inherit = 'product.template'

    @api.one
    @api.depends('product_variant_ids.product_tmpl_id')
    def _compute_product_variant_count(self):
        """For configurable products return the number of variants configured or 1
        as many views and methods trigger only when a template has at least one
        variant attached. Since we create them from the template we should have
        access to them always"""
        super(ProductTemplate, self)._compute_product_variant_count()
        if self.config_ok and not self.product_variant_count:
            self.product_variant_count = 1

    @api.multi
    @api.depends('attribute_line_ids.value_ids')
    def _compute_template_attr_vals(self):
        for product_tmpl in self.filtered(lambda t: t.config_ok):
            value_ids = product_tmpl.attribute_line_ids.mapped('value_ids')
            product_tmpl.attribute_line_val_ids = value_ids

    @api.multi
    @api.constrains('attribute_line_ids', 'attribute_value_line_ids')
    def check_attr_value_ids(self):
        for product_tmpl in self:
            attr_val_lines = product_tmpl.attribute_value_line_ids
            attr_val_ids = attr_val_lines.mapped('value_ids')
            if not attr_val_ids <= product_tmpl.attribute_line_val_ids:
                raise ValidationError(
                    _("All attribute values used in attribute value lines "
                      "must be defined in the attribute lines of the template")
                )

    @api.multi
    @api.constrains('attribute_value_line_ids')
    def _validate_unique_config(self):
        """Check for duplicate configurations for the same attribute value"""
        attr_val_line_vals = self.attribute_value_line_ids.read(
            ['value_id', 'value_ids'], load=False
        )
        attr_val_line_vals = [
            (l['value_id'], tuple(l['value_ids'])) for l in attr_val_line_vals
        ]
        if len(set(attr_val_line_vals)) != len(attr_val_line_vals):
            raise ValidationError(
                _('You cannot have a duplicate configuration for the '
                  'same value')
            )

    config_ok = fields.Boolean(string='Can be Configured')

    config_line_ids = fields.One2many(
        comodel_name='product.config.line',
        inverse_name='product_tmpl_id',
        string="Attribute Dependencies"
    )

    config_image_ids = fields.One2many(
        comodel_name='product.config.image',
        inverse_name='product_tmpl_id',
        string='Configuration Images'
    )

    attribute_value_line_ids = fields.One2many(
        comodel_name='product.attribute.value.line',
        inverse_name='product_tmpl_id',
        string="Attribute Value Lines"
    )

    attribute_line_val_ids = fields.Many2many(
        comodel_name='product.attribute.value',
        compute='_compute_template_attr_vals',
        store=False
    )

    config_step_line_ids = fields.One2many(
        comodel_name='product.config.step.line',
        inverse_name='product_tmpl_id',
        string='Configuration Lines'
    )

    mako_tmpl_name = fields.Text(
        string='Variant name',
        help="Generate Name based on Mako Template"
    )

    @api.multi
    def get_product_attribute_values_action(self):
        self.ensure_one()
        action = self.env.ref(
            'product.product_attribute_value_action').read()[0]
        value_ids = self.attribute_line_ids.mapped('value_ids').ids
        action['domain'] = [('id', 'in', value_ids)]
        context = safe_eval(action['context'], {'active_id': self.id})
        context.update({'active_id': self.id})
        action['context'] = context
        return action

    @api.multi
    @api.constrains('attribute_line_ids', 'attribute_line_ids.default_val')
    def _check_default_values(self):
        """Validate default values set on the product template"""
        default_val_ids = self.attribute_line_ids.filtered(
            lambda l: l.default_val).mapped('default_val').ids

        # TODO: Remove if cond when PR with raise error on github is merged
        cfg_session_obj = self.env['product.config.session']
        valid_conf = cfg_session_obj.validate_configuration(
            value_ids=default_val_ids, final=False
        )
        if not valid_conf:
            raise ValidationError(
                _('Default values provided generate an invalid configuration')
            )

    @api.multi
    @api.constrains('config_line_ids')
    def _check_default_value_domains(self):
        try:
            self._check_default_values()
        except ValidationError:
            raise ValidationError(
                _('Restrictions added make the current default values '
                  'generate an invalid configuration')
            )

    @api.multi
    def toggle_config(self):
        for record in self:
            record.config_ok = not record.config_ok

    @api.multi
    def create_variant_ids(self):
        """ Prevent configurable products from creating variants as these serve
            only as a template for the product configurator"""
        templates = self.filtered(lambda t: not t.config_ok)
        if not templates:
            return None
        return super(ProductTemplate, templates).create_variant_ids()

    @api.multi
    def unlink(self):
        """ Prevent the removal of configurable product templates
            from variants"""
        for template in self:
            variant_unlink = self.env.context.get('unlink_from_variant', False)
            if template.config_ok and variant_unlink:
                self -= template
        res = super(ProductTemplate, self).unlink()
        return res

    @api.multi
    def copy(self, default=None):
        res = super(ProductTemplate, self).copy(default=default)
        return res

    @api.multi
    def configure_product(self):
        return self.create_config_wizard()

    def create_config_wizard(self, model_name="product.configurator",
                             extra_vals=None, click_next=True):
        """create product configuration wizard
        - return action to launch wizard
        - click on next step based on value of click_next"""

        action = {
            'type': 'ir.actions.act_window',
            'res_model': 'product.configurator',
            'name': "Product Configurator",
            'view_mode': 'form',
            'target': 'new',
            'context': dict(
                self.env.context,
                wizard_model='product.configurator',
            ),
        }

        wizard_obj = self.env[model_name]
        wizard_vals = {
            'product_tmpl_id': self.id
        }
        if extra_vals:
            wizard_vals.update(extra_vals)
        wizard = wizard_obj.create(wizard_vals)
        if click_next:
            action = wizard.action_next_step()
        return action

class ProductProduct(models.Model):
    _inherit = 'product.product'
    _rec_name = 'config_name'

    def _get_conversions_dict(self):
        conversions = {
            'float': float,
            'int': int
        }
        return conversions

    @api.constrains('attribute_value_ids')
    def _check_duplicate_product(self):
        if not self.config_ok:
            return None

        # At the moment, I don't have enough confidence with my understanding
        # of binary attributes, so will leave these as not matching...
        # In theory, they should just work, if they are set to "non search"
        # in custom field def!
        # TODO: Check the logic with binary attributes
        if not self.value_custom_ids.filtered(lambda cv: cv.attachment_ids):
            config_session_obj = self.env['product.config.session']
            custom_vals = {
                cv.attribute_id.id: cv.value
                for cv in self.value_custom_ids
            }
            duplicates = config_session_obj.search_variant(
                product_tmpl_id=self.product_tmpl_id.id,
                value_ids=self.attribute_value_ids.ids,
                custom_vals=custom_vals
            ).filtered(lambda p: p.id != self.id)

            if duplicates:
                raise ValidationError(
                    _("Configurable Products cannot have duplicates "
                      "(identical attribute values)")
                )

    @api.model
    def _get_config_name(self):
        return self.name

    @api.model
    def _get_mako_context(self, buf):
        return Context(
            buf, product=self,
            attribute_values=self.attribute_value_ids,
            steps=self.product_tmpl_id.config_step_line_ids,
            template=self.product_tmpl_id)

    @api.model
    def _get_mako_tmpl_name(self):
        if self.mako_tmpl_name:
            try:
                mytemplate = Template(self.mako_tmpl_name or '')
                buf = StringIO()
                ctx = self._get_mako_context(buf)
                mytemplate.render_context(ctx)
                return buf.getvalue()
            except Exception:
                _logger.error(
                    _("Error while calculating mako product name: %s") %
                    self.display_name)
        return self.display_name

    @api.depends('attribute_value_ids.price_ids.weight_extra',
                 'attribute_value_ids.price_ids.product_tmpl_id')
    def _compute_product_weight_extra(self):
        for product in self:
            weight_extra = 0.0
            attr_prices = product.mapped('attribute_value_ids.price_ids')
            for attribute_price in attr_prices:
                if attribute_price.product_tmpl_id == product.product_tmpl_id:
                    weight_extra += attribute_price.weight_extra
            product.weight_extra = weight_extra

    @api.depends('product_tmpl_id.weight', 'weight_extra')
    def _compute_product_weight(self):
        for product in self:
            tmpl_weight = product.product_tmpl_id.weight
            product.weight = tmpl_weight + product.weight_extra

    config_name = fields.Char(
        string="Name",
        size=256,
        compute='_compute_name',
    )
    value_custom_ids = fields.One2many(
        comodel_name='product.attribute.value.custom',
        inverse_name='product_id',
        string='Custom Values',
        readonly=True
    )
    price_extra = fields.Float(
        compute='_compute_product_price_extra',
        string='Variant Extra Price',
        help="This is the sum of the extra price of all attributes",
        digits=dp.get_precision('Product Price')
    )
    weight_extra = fields.Float(
        string='Weight Extra',
        compute='_compute_product_weight_extra'
    )

    weight = fields.Float(compute='_compute_product_weight')

    @api.multi
    def get_product_attribute_values_action(self):
        self.ensure_one()
        action = self.env.ref(
            'product.product_attribute_value_action').read()[0]
        value_ids = self.attribute_value_ids.ids
        action['domain'] = [('id', 'in', value_ids)]
        context = safe_eval(
            action['context'],
            {'active_id': self.product_tmpl_id.id}
        )
        context.update({'active_id': self.product_tmpl_id.id})
        action['context'] = context
        return action

    @api.multi
    def _check_attribute_value_ids(self):
        """ Removing multi contraint attribute to enable multi selection. """
        return True

    _constraints = [
        (_check_attribute_value_ids, None, ['attribute_value_ids'])
    ]

    @api.model
    def fields_view_get(self, view_id=None, view_type='form',
                        toolbar=False, submenu=False):
        """ For configurable products switch the name field with the config_name
            so as to keep the view intact in whatever form it is at the moment
            of execution and not duplicate the original just for the sole
            purpose of displaying the proper name"""
        res = super(ProductProduct, self).fields_view_get(
            view_id=view_id, view_type=view_type,
            toolbar=toolbar, submenu=submenu
        )
        if self.env.context.get('default_config_ok'):
            xml_view = etree.fromstring(res['arch'])
            xml_name = xml_view.xpath("//field[@name='name']")
            xml_label = xml_view.xpath("//label[@for='name']")
            if xml_name:
                xml_name[0].attrib['name'] = 'config_name'
                if xml_label:
                    xml_label[0].attrib['for'] = 'config_name'
                view_obj = self.env['ir.ui.view']
                xarch, xfields = view_obj.postprocess_and_fields(self._name,
                                                                 xml_view,
                                                                 view_id)
                res['arch'] = xarch
                res['fields'] = xfields
        return res

    @api.multi
    def unlink(self):
        """ Signal unlink from product variant through context so
            removal can be stopped for configurable templates """
        ctx = dict(self.env.context, unlink_from_variant=True)
        self.env.context = ctx
        return super(ProductProduct, self).unlink()

    @api.multi
    def _compute_name(self):
        """ Compute the name of the configurable products and use template
            name for others"""
        for product in self:
            if product.config_ok:
                product.config_name = product._get_config_name()
            else:
                product.config_name = product.name

    @api.multi
    def reconfigure_product(self):
        """ Creates and launches a product configurator wizard with a linked
        template and variant in order to re-configure an existing product.
        It is essentially a shortcut to pre-fill configuration
        data of a variant"""

        extra_vals = {
            'product_id': self.id,
        }
        return self.product_tmpl_id.create_config_wizard(
            extra_vals=extra_vals)
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
import logging
# import json
import boto3
from core import ff_utils
from core.utils import Tibanna, ensure_list

LOG = logging.getLogger(__name__)
s3 = boto3.resource('s3')

def handler(event, context):
    '''
    this is generic function to run awsem workflow
    based on the data passed in

    workflow_uuid : for now, pass this on. Later we can add a code to automatically retrieve this from app_name.
    Note multiple workflow_uuids can be available for an app_name
    (different versions of the same app could have a different uuid)
    '''
    # get incomming data
    input_file_list = event.get('input_files')
    app_name = event.get('app_name')
    print(app_name)
    workflow_uuid = event.get('workflow_uuid')
    output_bucket = event.get('output_bucket')
    parameters = ff_utils.convert_param(event.get('parameters'), True)
    tibanna_settings = event.get('_tibanna', {})
    # if they don't pass in env guess it from output_bucket
    env = tibanna_settings.get('env', '-'.join(output_bucket.split('-')[1:-1]))
    # tibanna provides access to keys based on env and stuff like that
    tibanna = Tibanna(env, s3_keys=event.get('s3_keys'), ff_keys=event.get('ff_keys'),
                      settings=tibanna_settings)

    args = dict()

    # get argument format & type info from workflow
    workflow_info = ff_utils.get_metadata(workflow_uuid, key=tibanna.ff_keys)
    LOG.info("workflow info  %s" % workflow_info)
    if 'error' in workflow_info.get('@type', []):
        raise Exception("FATAL, can't lookup workflow info for % fourfront" % workflow_uuid)

    # get cwl info from workflow_info
    for k in ['app_name', 'app_version', 'cwl_directory_url', 'cwl_main_filename', 'cwl_child_filenames']:
        LOG.info(workflow_info.get(k))
        args[k] = workflow_info.get(k)

    # get format-extension map
    try:
        fp_schema = ff_utils.get_metadata("profiles/file_processed.json", key=tibanna.ff_keys)
        fe_map = fp_schema.get('file_format_file_extension')
    except Exception as e:
        LOG.error("Can't get format-extension map from file_processed schema. %s\n" % e)

    # processed file metadata
    output_files = []
    try:
        if 'arguments' in workflow_info:
            pf_meta = []
            for arg in workflow_info.get('arguments'):
                if (arg.get('argument_type') in
                   ['Output processed file', 'Output report file', 'Output QC file']):

                    of = dict()
                    of['workflow_argument_name'] = arg.get('workflow_argument_name')
                    of['type'] = arg.get('argument_type')
                    if 'argument_format' in arg:
                        # These are not processed files but report or QC files.
                        if 'secondary_file_formats' in arg:
                            of['secondary_file_formats'] = arg.get('secondary_file_formats')
                            extra_files = [{"file_formats": v} for v in of['secondary_file_formats']]
                        pf = ff_utils.ProcessedFileMetadata(file_format=arg.get('argument_format'),
                                                            extra_files=extra_files)
                        try:
                            resp = pf.post(key=tibanna.ff_keys)  # actually post processed file metadata here
                            resp = resp.get('@graph')[0]
                            of['upload_key'] = resp.get('upload_key')
                            of['value'] = resp.get('uuid')
                        except Exception as e:
                            LOG.error("Failed to post Processed file metadata. %s\n" % e)
                            LOG.error("resp" + str(resp) + "\n")
                            raise e
                        of['format'] = arg.get('argument_format')
                        of['extension'] = fe_map.get(arg.get('argument_format'))
                        pf_meta.append(pf)
                    output_files.append(of)

    except Exception as e:
        LOG.error("output_files = " + str(output_files) + "\n")
        LOG.error("Can't prepare output_files information. %s\n" % e)
        raise e

    # create the ff_meta output info
    input_files = []
    for input_file in input_file_list:
        for idx, uuid in enumerate(ensure_list(input_file['uuid'])):
            input_files.append({'workflow_argument_name': input_file['workflow_argument_name'],
                                'value': uuid, 'ordinal': idx + 1})
    LOG.info("input_files is %s" % input_files)

    ff_meta = ff_utils.create_ffmeta_awsem(workflow_uuid, app_name, input_files,
                                           run_url=tibanna.settings.get('url', ''),
                                           output_files=output_files, parameters=parameters)

    LOG.info("ff_meta is %s" % ff_meta.__dict__)

    # store metadata so we know the run has started
    ff_meta.post(key=tibanna.ff_keys)

    # input file args for awsem
    args['input_files'] = dict()
    for input_file in input_file_list:
        if isinstance(input_file['uuid'], unicode):
            input_file['uuid'] = input_file['uuid'].encode('utf-8')
        if isinstance(input_file['object_key'], unicode):
            input_file['object_key'] = input_file['object_key'].encode('utf-8')
        if isinstance(input_file['uuid'], str) and isinstance(input_file['object_key'], str):
            object_key = input_file['uuid'] + '/' + input_file['object_key']
        elif (isinstance(input_file['uuid'], list) and
              isinstance(input_file['object_key'], list) and
              len(input_file['uuid']) == len(input_file['object_key'])):

            object_key = [a + '/' + b for a, b in zip(input_file['uuid'], input_file['object_key'])]
        else:
            raise Exception("input_file uuid and object_key should match in their type and length (if lists) : " +
                            "type{}{} length{}{}".format(type(input_file['uuid']), type(input_file['object_key']),
                                                         len(input_file['uuid']), len(input_file['object_key'])))
        args['input_files'].update({input_file['workflow_argument_name']: {
                                    'bucket_name': input_file['bucket_name'],
                                    'object_key': object_key}})

    LOG.info("input_file_args is %s" % args['input_files'])
    args['secondary_files'] = dict()   # temporary, later fill in based on the attachment information

    # parameters
    args['input_parameters'] = event.get('parameters')

    # output target
    args['output_target'] = dict()
    args['secondary_output_target'] = dict()
    for of in ff_meta.output_files:
        arg_name = of.get('workflow_argument_name')
        if of.get('type') == 'Output processed file':
            args['output_target'][arg_name] = of.get('upload_key')
        else:
            args['output_target'][arg_name] = ff_meta.uuid + '/' + arg_name
        if 'secondary_file_formats' in of:
            # takes only the first secondary file.
            args['secondary_output_target'][arg_name] \
                = of.get('upload_key').replace(of.get('extension'), of.get('secondary_file_formats')[0])

    # output bucket
    args['output_S3_bucket'] = event.get('output_bucket')

    event.update({"ff_meta": ff_meta.as_dict(),
                  'pf_meta': [meta.as_dict() for meta in pf_meta],
                  "_tibanna": tibanna.as_dict(),
                  "args": args
                  })
    return(event)
<commit_msg>secondary format vs extension problem fixed<commit_after># -*- coding: utf-8 -*-
import logging
# import json
import boto3
from core import ff_utils
from core.utils import Tibanna, ensure_list

LOG = logging.getLogger(__name__)
s3 = boto3.resource('s3')

def handler(event, context):
    '''
    this is generic function to run awsem workflow
    based on the data passed in

    workflow_uuid : for now, pass this on. Later we can add a code to automatically retrieve this from app_name.
    Note multiple workflow_uuids can be available for an app_name
    (different versions of the same app could have a different uuid)
    '''
    # get incomming data
    input_file_list = event.get('input_files')
    app_name = event.get('app_name')
    print(app_name)
    workflow_uuid = event.get('workflow_uuid')
    output_bucket = event.get('output_bucket')
    parameters = ff_utils.convert_param(event.get('parameters'), True)
    tibanna_settings = event.get('_tibanna', {})
    # if they don't pass in env guess it from output_bucket
    env = tibanna_settings.get('env', '-'.join(output_bucket.split('-')[1:-1]))
    # tibanna provides access to keys based on env and stuff like that
    tibanna = Tibanna(env, s3_keys=event.get('s3_keys'), ff_keys=event.get('ff_keys'),
                      settings=tibanna_settings)

    args = dict()

    # get argument format & type info from workflow
    workflow_info = ff_utils.get_metadata(workflow_uuid, key=tibanna.ff_keys)
    LOG.info("workflow info  %s" % workflow_info)
    if 'error' in workflow_info.get('@type', []):
        raise Exception("FATAL, can't lookup workflow info for % fourfront" % workflow_uuid)

    # get cwl info from workflow_info
    for k in ['app_name', 'app_version', 'cwl_directory_url', 'cwl_main_filename', 'cwl_child_filenames']:
        LOG.info(workflow_info.get(k))
        args[k] = workflow_info.get(k)

    # get format-extension map
    try:
        fp_schema = ff_utils.get_metadata("profiles/file_processed.json", key=tibanna.ff_keys)
        fe_map = fp_schema.get('file_format_file_extension')
    except Exception as e:
        LOG.error("Can't get format-extension map from file_processed schema. %s\n" % e)

    # processed file metadata
    output_files = []
    try:
        if 'arguments' in workflow_info:
            pf_meta = []
            for arg in workflow_info.get('arguments'):
                if (arg.get('argument_type') in
                   ['Output processed file', 'Output report file', 'Output QC file']):

                    of = dict()
                    of['workflow_argument_name'] = arg.get('workflow_argument_name')
                    of['type'] = arg.get('argument_type')
                    if 'argument_format' in arg:
                        # These are not processed files but report or QC files.
                        if 'secondary_file_formats' in arg:
                            of['secondary_file_formats'] = arg.get('secondary_file_formats')
                            of['secondary_file_extensions'] = [fe_map.get(v) for v in arg.get('secondary_file_formats')]
                            extra_files = [{"file_formats": v} for v in of['secondary_file_formats']]
                        else:
                            extra_files = []
                        pf = ff_utils.ProcessedFileMetadata(file_format=arg.get('argument_format'),
                                                            extra_files=extra_files)
                        try:
                            resp = pf.post(key=tibanna.ff_keys)  # actually post processed file metadata here
                            resp = resp.get('@graph')[0]
                            of['upload_key'] = resp.get('upload_key')
                            of['value'] = resp.get('uuid')
                        except Exception as e:
                            LOG.error("Failed to post Processed file metadata. %s\n" % e)
                            LOG.error("resp" + str(resp) + "\n")
                            raise e
                        of['format'] = arg.get('argument_format')
                        of['extension'] = fe_map.get(arg.get('argument_format'))
                        pf_meta.append(pf)
                    output_files.append(of)

    except Exception as e:
        LOG.error("output_files = " + str(output_files) + "\n")
        LOG.error("Can't prepare output_files information. %s\n" % e)
        raise e

    # create the ff_meta output info
    input_files = []
    for input_file in input_file_list:
        for idx, uuid in enumerate(ensure_list(input_file['uuid'])):
            input_files.append({'workflow_argument_name': input_file['workflow_argument_name'],
                                'value': uuid, 'ordinal': idx + 1})
    LOG.info("input_files is %s" % input_files)

    ff_meta = ff_utils.create_ffmeta_awsem(workflow_uuid, app_name, input_files,
                                           run_url=tibanna.settings.get('url', ''),
                                           output_files=output_files, parameters=parameters)

    LOG.info("ff_meta is %s" % ff_meta.__dict__)

    # store metadata so we know the run has started
    ff_meta.post(key=tibanna.ff_keys)

    # input file args for awsem
    args['input_files'] = dict()
    for input_file in input_file_list:
        if isinstance(input_file['uuid'], unicode):
            input_file['uuid'] = input_file['uuid'].encode('utf-8')
        if isinstance(input_file['object_key'], unicode):
            input_file['object_key'] = input_file['object_key'].encode('utf-8')
        if isinstance(input_file['uuid'], str) and isinstance(input_file['object_key'], str):
            object_key = input_file['uuid'] + '/' + input_file['object_key']
        elif (isinstance(input_file['uuid'], list) and
              isinstance(input_file['object_key'], list) and
              len(input_file['uuid']) == len(input_file['object_key'])):

            object_key = [a + '/' + b for a, b in zip(input_file['uuid'], input_file['object_key'])]
        else:
            raise Exception("input_file uuid and object_key should match in their type and length (if lists) : " +
                            "type{}{} length{}{}".format(type(input_file['uuid']), type(input_file['object_key']),
                                                         len(input_file['uuid']), len(input_file['object_key'])))
        args['input_files'].update({input_file['workflow_argument_name']: {
                                    'bucket_name': input_file['bucket_name'],
                                    'object_key': object_key}})

    LOG.info("input_file_args is %s" % args['input_files'])
    args['secondary_files'] = dict()   # temporary, later fill in based on the attachment information

    # parameters
    args['input_parameters'] = event.get('parameters')

    # output target
    args['output_target'] = dict()
    args['secondary_output_target'] = dict()
    for of in ff_meta.output_files:
        arg_name = of.get('workflow_argument_name')
        if of.get('type') == 'Output processed file':
            args['output_target'][arg_name] = of.get('upload_key')
        else:
            args['output_target'][arg_name] = ff_meta.uuid + '/' + arg_name
        if 'secondary_file_formats' in of:
            # takes only the first secondary file.
            args['secondary_output_target'][arg_name] \
                = of.get('upload_key').replace(of.get('extension'), of.get('secondary_file_extensions')[0])

    # output bucket
    args['output_S3_bucket'] = event.get('output_bucket')

    event.update({"ff_meta": ff_meta.as_dict(),
                  'pf_meta': [meta.as_dict() for meta in pf_meta],
                  "_tibanna": tibanna.as_dict(),
                  "args": args
                  })
    return(event)
<|endoftext|>


<commit_before>"""
Internal abstract base class for all dimensionality reduction plots.
Not intended to be user-facing.
"""
from __future__ import division
import colorsys
import itertools
import matplotlib as mpl
from textwrap import dedent
import warnings

import matplotlib.pyplot as plt
from matplotlib import offsetbox
import numpy as np
import pandas as pd
import seaborn as sns

class _ReducedPlotter(object):
    """Generic object for plotting high-dimensional data on 2d space"""

    def establish_reducer(self, reducer, n_components=None, smusher_kws=None):

        try:
            # Create a brand new dimensionality reducer
            # (matrix decomposer/manifold learner)

            smusher_kws = {} if smusher_kws is None else smusher_kws
            smusher_kws.setdefault('n_components', n_components)

            self.reducer = reducer(**smusher_kws)
        except TypeError:
            # We're using a pre-existing, user-supplied reducer
            self.reducer = reducer

    def establish_variables(self, data):
        """Convert input specification into a common representation."""

        # Option 1a:
        # The input data is a Pandas DataFrame
        # ------------------------------------
        if isinstance(data, pd.DataFrame):
            high_dimensional_data = data
            group_label = data.index.name
            value_label = data.columns.name

        # Option 1b:
        # The input data is an array or list
        # ----------------------------------
        else:
            if len(data.shape) == 2:
                nr, nc = data.shape
                if nr == 1 or nc == 1:
                    error = ("Input `data` must have "
                             "exactly 2 dimensions, where both "
                             "have at least 2 components")
                    raise ValueError(error)
                else:
                    high_dimensional_data = pd.DataFrame(data)
            else:
                error = ("Input `data` must have "
                         "exactly 2 dimensions")
                raise ValueError(error)

            group_label = None
            value_label = None

        # Assign object attributes
        # ------------------------
        self.high_dimensional_data = high_dimensional_data
        self.group_label = group_label
        self.value_label = value_label

        self.samples = self.high_dimensional_data.index
        self.features = self.high_dimensional_data.columns
        self.n_samples = len(self.samples)
        self.n_features = len(self.features)

    def establish_colors(self, color, hue, hue_order, palette, saturation):
        """Get a list of colors for the main component of the plots."""

        if hue is None:
            if hue_order is not None and len(hue) != self.n_samples:
                error = 'Cannot specify "hue_order" without specifying "hue"'
                raise ValueError(error)

            if palette is None:
                n_colors = 1
            else:
                n_colors = self.n_samples
        else:
            if hue_order is None:
                n_colors = self.high_dimensional_data.groupby(hue).size()
            else:
                n_colors = len(hue_order)
        n_colors = None

        if color is None:
            # No color_groupby is specified
            if palette is None:
                # Auto-assigned palette
                if hue is None:
                    current_palette = sns.utils.get_color_cycle()
                    color = current_palette[0]
                    if hue_order is None:
                        # Auto-assign one color_groupby to all samples from current
                        # color_groupby cycle
                        n_colors = 1
                        color_groupby = self._maybe_make_grouper(color)
                    else:
                        if len(hue_order) == self.n_samples:
                            n_colors = self.n_samples
                            colors = sns.light_palette(color,
                                                       n_colors=n_colors)
                            color_groupby = self.Series(
                                colors, index=self.samples)
                        else:
                            error = 'Cannot interpret "hue_order" when "hue" is ' \
                                    'not specified [or len(hue_order) !=' \
                                    ' len(data.index)]'
                            raise ValueError(error)

                else:
                    # Use the hue grouping, possibly with an order (handled
                    # within _maybe_make_grouper)
                    color_groupby = self._maybe_make_grouper(hue,
                                                             order=hue_order,
                                                             dtype=str)
                    n_colors = len(self.high_dimensional_data.groupby(
                        color_groupby).size())
            else:
                # User-defined palette
                if hue is None:
                    # Assign every sample a different color_groupby from palette
                    n_colors = self.n_samples
                    colors = sns.color_palette(palette, n_colors=n_colors)

                    index = self.samples if hue_order is None else hue_order
                    color_groupby = pd.Series(colors, index=index)
                else:
                    # Assign every group a different color_groupby from palette

                    grouped = self.high_dimensional_data.groupby(hue)
                    size = grouped.size()
                    n_colors = size.shape[0]
                    palette = sns.color_palette(palette, n_colors=n_colors)

                    color_groupby = self._color_grouper_from_palette(
                        grouped, palette, hue_order)
        else:
            # Single color_groupby is provided
            if palette is None:
                if hue is None:
                    if hue_order is None:
                        color_groupby = self._maybe_make_grouper(color)
                        n_colors = 1
                    else:
                        if len(hue_order) == self.n_samples:
                            n_colors = self.n_samples
                            colors = sns.light_palette(color,
                                                       n_colors=n_colors)
                            color_groupby = self.Series(colors,
                                                        index=self.samples)
                        else:
                            error = 'Cannot interpret "hue_order" when "hue"' \
                                    ' is not specified [or len(hue_order) !=' \
                                    ' len(data.index)]'
                            raise ValueError(error)
                else:
                    grouped = self.high_dimensional_data.groupby(hue)
                    size = grouped.size()
                    self.n_colors = len(size)
                    palette = sns.light_palette(color, n_colors=n_colors)

                    color_groupby = self._color_grouper_from_palette(
                        grouped, palette, hue_order)
            else:
                error = 'Cannot specify both "palette" and "color"'
                raise ValueError(error)

        # Assign object attributes
        self.n_colors = n_colors
        self.color = color_groupby

    def _maybe_make_grouper(self, attribute, order=None, dtype=None):
        """Create a Series from a single attribute, else make categorical

        Parameters
        ----------
        attribute : object
            Either a single item to create into a series, or a series mapping
            each sample to an attribute (e.g. the plotting symbol 'o' or
            linewidth 1)
        order : list
            The order to create the attributes into
        dtype : type
            If "attribute" is of this type (as in, it is a single item), then
            create a single series. This is for consistency so that every
            possible plotting attribute can be used in a "groupby"

        Returns
        -------
        grouper : pandas.Series
            A mapping of the high dimensional data samples to the attribute
        """
        if dtype is None or isinstance(attribute, dtype):
            # Use this single attribute for everything
            return pd.Series([attribute]*self.n_samples, index=self.samples)
        else:
            order = sns.utils.categorical_order(attribute, order)
            attribute = pd.Categorical(attribute, categories=order,
                                       ordered=True)
            return pd.Series(attribute, index=self.samples)

    def _color_grouper_from_palette(self, grouped, palette, hue_order):
        color_tuples = [zip(df.index, [palette[i]] * df.shape[0])
                        for i, (g, df) in enumerate(grouped)]

        colors = dict(itertools.chain(*color_tuples))
        color_groupby = self._maybe_make_grouper(colors, hue_order)
        return color_groupby

    def establish_symbols(self, marker, marker_order, text, text_order,
                          linewidth, linewidth_order, edgecolor,
                          edgecolor_order):
        """Figure out what to put on the axes for each sample in the data"""
        if isinstance(text, bool):
            # Option 1: Text is a boolean
            if text:
                # 1a: text=True, so use the sample names of data as the
                # plotting symbol
                symbol = pd.Series(
                    map(str, self.samples), index=self.samples)
            else:
                # 1b: text=False, so use the specified marker for each sample
                symbol = self._maybe_make_grouper(marker, marker_order, str)

        else:
            # Assume "text" is a mapping from row names (sample ids) of the
            # data to text labels
            text_order = sns.utils.categorical_order(text, text_order)
            symbol = pd.Series(pd.Categorical(text, categories=text_order,
                                              ordered=True),
                               index=self.samples)
            if marker is not None:
                warnings.warn('Overriding plotting symbol from "marker" with '
                              'values in "text"')

            # Turn text into a boolean
            text = True

        self.symbol = symbol

        # Set these even if text=True because they won't be used when plotting
        # the text anyways
        self.linewidth = self._maybe_make_grouper(linewidth, linewidth_order,
                                                  (int, float))
        self.edgecolor = self._maybe_make_grouper(edgecolor, edgecolor_order,
                                                  str)
        self.text = text

    def symbolplotter(self, xs, ys, symbol, linewidth, edgecolor, **kwargs):
        """Plots either a matplotlib marker or a string at each data position

        Wraps plt.text and plt.plot

        Parameters
        ----------
        xs : array-like
            List of x positions for data
        ys : array-like
            List of y-positions for data
        symbol : str
            What to plot at each (x, y) data position
        text : bool
            If true, then "symboL" is assumed to be a string and iterates over
            each data point individually, using plt.text to position the text.
            Otherwise, "symbol" is a matplotlib marker and uses plt.plot for
            plotting
        kwargs
            Any other keyword arguments to plt.text or plt.plot
        """
        # If both the x- and y- positions don't have data, don't do anything
        if xs.empty and ys.empty:
            return

        if self.text:
            # Plot each (x, y) position as text
            for x, y in zip(xs, ys):
                plt.text(x, y, symbol, **kwargs)
        else:
            # use plt.plot instead of plt.scatter for speed, since plotting all
            # the same marker shape and color and linestyle
            plt.plot(xs, ys, 'o', marker=symbol, linewidth=linewidth,
                     edgecolor=edgecolor, **kwargs)

    def annotate_axes(self, ax):
        """Add descriptive labels to an Axes object."""
        if self.orient == "v":
            xlabel, ylabel = self.group_label, self.value_label
        else:
            xlabel, ylabel = self.value_label, self.group_label

        if xlabel is not None:
            ax.set_xlabel(xlabel)
        if ylabel is not None:
            ax.set_ylabel(ylabel)

        if self.orient == "v":
            ax.set_xticks(np.arange(len(self.reduced_data)))
            ax.set_xticklabels(self.group_names)
        else:
            ax.set_yticks(np.arange(len(self.reduced_data)))
            ax.set_yticklabels(self.group_names)

        if self.orient == "v":
            ax.xaxis.grid(False)
            ax.set_xlim(-.5, len(self.reduced_data) - .5)
        else:
            ax.yaxis.grid(False)
            ax.set_ylim(-.5, len(self.reduced_data) - .5)

        if self.hue_names is not None:
            leg = ax.legend(loc="best")
            if self.hue_title is not None:
                leg.set_title(self.hue_title)

                # Set the title size a roundabout way to maintain
                # compatability with matplotlib 1.1
                try:
                    title_size = mpl.rcParams["axes.labelsize"] * .85
                except TypeError:  # labelsize is something like "large"
                    title_size = mpl.rcParams["axes.labelsize"]
                prop = mpl.font_manager.FontProperties(size=title_size)
                leg._legend_title_box._text.set_font_properties(prop)

    def add_legend_data(self, ax, color, label):
        """Add a dummy patch object so we can get legend data."""
        rect = plt.Rectangle([0, 0], 0, 0,
                             linewidth=self.linewidth / 2,
                             edgecolor=self.gray,
                             facecolor=color,
                             label=label)
        ax.add_patch(rect)

    def draw_markers(self, ax, x=0, y=1, **kwargs):
        """Plot each sample in the data in the reduced space"""

        # Iterate over all the possible modifications of the points
        for color, color_df in self.reduced_data.groupby(self.color):
            for symbol, symbol_df in color_df.groupby(self.symbol):
                for lw, lw_df in symbol_df.groupby(self.linewidth):
                    for ec, ec_df in lw_df.groupby(self.edgecolor):
                        # and finally ... actually plot the data!
                        self.symbolplotter(ec_df[:, x], ec_df[:, y],
                                           symbol=symbol, color=color,
                                           ax=ax, linewidth=lw, edgecolor=ec,
                                           **kwargs)

    def draw_images(self, ax, images):
        if hasattr(offsetbox, 'AnnotationBbox'):
            # only print thumbnails with matplotlib > 1.0
            shown_images = np.array([[1., 1.]])  # just something big
            for i in range(self.reduced_data.shape[0]):
                dist = np.sum((self.reduced_data[i] - shown_images) ** 2, 1)
                if np.min(dist) < 5e-4 * np.product(images[0].shape):
                    # don't show points that are too close
                    continue
                shown_images = np.r_[shown_images, [self.reduced_data[i]]]
                imagebox = offsetbox.AnnotationBbox(
                    offsetbox.OffsetImage(images[i], cmap=plt.cm.gray),
                    self.reduced_data[i])
                ax.add_artist(imagebox)

    def compute_reduction(self):
        """Use reducer to smush high-dimensional data into a smaller space"""
        matrix = self.reducer.fit_transform(self.high_dimensional_data)
        columns = np.arange(matrix.shape[1], dtype=int) + 1
        self.reduced_data = pd.DataFrame(
            matrix, columns=columns, index=self.high_dimensional_data.index)

_reducer_docs = dict(
    input_params=dedent("""
    data : DataFrame, array, or list of arrays
        Dataset to reduce dimensionality and plot.
        """),
    component_input_params=dedent("""\
    x, y : int
        1-based integer of components to plot on "x" and "y" axes. Defaults
        are ``x=1``, ``y=2``.\
        """),

    groupby_input_params=dedent("""\
    hue, marker : mapping (dict or pandas.Series)
        A dict or pandas.Series mapping the row names of ``data`` to a
        separate category to plot as separate colors (hue) or plotting
        symbols (marker). See examples for interpretation.\
        """),

    # From seaborn.categorical
    color=dedent("""\
    color : matplotlib color, optional
        Color for all of the elements, or seed for :func:`light_palette` when
        using hue nesting.\
    """),
    palette=dedent("""\
    palette : palette name, list, or dict, optional
        Color palette that maps either the grouping variable or the hue
        variable. If the palette is a dictionary, keys should be names of
        levels and values should be matplotlib colors.\
    """),
    saturation=dedent("""\
    saturation : float, optional
        Proportion of the original saturation to draw colors at. Large patches
        often look better with slightly desaturated colors, but set this to
        ``1`` if you want the plot colors to perfectly match the input color
        spec.\
    """),
    ax_in=dedent("""\
    ax : matplotlib Axes, optional
        Axes object to draw the plot onto, otherwise uses the current Axes.\
    """),
)
<commit_msg>Removed n_colors from beginning<commit_after>"""
Internal abstract base class for all dimensionality reduction plots.
Not intended to be user-facing.
"""
from __future__ import division
import colorsys
import itertools
import matplotlib as mpl
from textwrap import dedent
import warnings

import matplotlib.pyplot as plt
from matplotlib import offsetbox
import numpy as np
import pandas as pd
import seaborn as sns

class _ReducedPlotter(object):
    """Generic object for plotting high-dimensional data on 2d space"""

    def establish_reducer(self, reducer, n_components=None, smusher_kws=None):

        try:
            # Create a brand new dimensionality reducer
            # (matrix decomposer/manifold learner)

            smusher_kws = {} if smusher_kws is None else smusher_kws
            smusher_kws.setdefault('n_components', n_components)

            self.reducer = reducer(**smusher_kws)
        except TypeError:
            # We're using a pre-existing, user-supplied reducer
            self.reducer = reducer

    def establish_variables(self, data):
        """Convert input specification into a common representation."""

        # Option 1a:
        # The input data is a Pandas DataFrame
        # ------------------------------------
        if isinstance(data, pd.DataFrame):
            high_dimensional_data = data
            group_label = data.index.name
            value_label = data.columns.name

        # Option 1b:
        # The input data is an array or list
        # ----------------------------------
        else:
            if len(data.shape) == 2:
                nr, nc = data.shape
                if nr == 1 or nc == 1:
                    error = ("Input `data` must have "
                             "exactly 2 dimensions, where both "
                             "have at least 2 components")
                    raise ValueError(error)
                else:
                    high_dimensional_data = pd.DataFrame(data)
            else:
                error = ("Input `data` must have "
                         "exactly 2 dimensions")
                raise ValueError(error)

            group_label = None
            value_label = None

        # Assign object attributes
        # ------------------------
        self.high_dimensional_data = high_dimensional_data
        self.group_label = group_label
        self.value_label = value_label

        self.samples = self.high_dimensional_data.index
        self.features = self.high_dimensional_data.columns
        self.n_samples = len(self.samples)
        self.n_features = len(self.features)

    def establish_colors(self, color, hue, hue_order, palette, saturation):
        """Get a list of colors for the main component of the plots."""
        n_colors = None

        if color is None:
            # No color_groupby is specified
            if palette is None:
                # Auto-assigned palette
                if hue is None:
                    current_palette = sns.utils.get_color_cycle()
                    color = current_palette[0]
                    if hue_order is None:
                        # Auto-assign one color_groupby to all samples from current
                        # color_groupby cycle
                        n_colors = 1
                        color_groupby = self._maybe_make_grouper(color)
                    else:
                        if len(hue_order) == self.n_samples:
                            n_colors = self.n_samples
                            colors = sns.light_palette(color,
                                                       n_colors=n_colors)
                            color_groupby = self.Series(
                                colors, index=self.samples)
                        else:
                            error = 'Cannot interpret "hue_order" when "hue" is ' \
                                    'not specified [or len(hue_order) !=' \
                                    ' len(data.index)]'
                            raise ValueError(error)

                else:
                    # Use the hue grouping, possibly with an order (handled
                    # within _maybe_make_grouper)
                    color_groupby = self._maybe_make_grouper(hue,
                                                             order=hue_order,
                                                             dtype=str)
                    n_colors = len(self.high_dimensional_data.groupby(
                        color_groupby).size())
            else:
                # User-defined palette
                if hue is None:
                    # Assign every sample a different color_groupby from palette
                    n_colors = self.n_samples
                    colors = sns.color_palette(palette, n_colors=n_colors)

                    index = self.samples if hue_order is None else hue_order
                    color_groupby = pd.Series(colors, index=index)
                else:
                    # Assign every group a different color_groupby from palette

                    grouped = self.high_dimensional_data.groupby(hue)
                    size = grouped.size()
                    n_colors = size.shape[0]
                    palette = sns.color_palette(palette, n_colors=n_colors)

                    color_groupby = self._color_grouper_from_palette(
                        grouped, palette, hue_order)
        else:
            # Single color_groupby is provided
            if palette is None:
                if hue is None:
                    if hue_order is None:
                        color_groupby = self._maybe_make_grouper(color)
                        n_colors = 1
                    else:
                        if len(hue_order) == self.n_samples:
                            n_colors = self.n_samples
                            colors = sns.light_palette(color,
                                                       n_colors=n_colors)
                            color_groupby = self.Series(colors,
                                                        index=self.samples)
                        else:
                            error = 'Cannot interpret "hue_order" when "hue"' \
                                    ' is not specified [or len(hue_order) !=' \
                                    ' len(data.index)]'
                            raise ValueError(error)
                else:
                    grouped = self.high_dimensional_data.groupby(hue)
                    size = grouped.size()
                    self.n_colors = len(size)
                    palette = sns.light_palette(color, n_colors=n_colors)

                    color_groupby = self._color_grouper_from_palette(
                        grouped, palette, hue_order)
            else:
                error = 'Cannot specify both "palette" and "color"'
                raise ValueError(error)

        # Assign object attributes
        self.n_colors = n_colors
        self.color = color_groupby

    def _maybe_make_grouper(self, attribute, order=None, dtype=None):
        """Create a Series from a single attribute, else make categorical

        Parameters
        ----------
        attribute : object
            Either a single item to create into a series, or a series mapping
            each sample to an attribute (e.g. the plotting symbol 'o' or
            linewidth 1)
        order : list
            The order to create the attributes into
        dtype : type
            If "attribute" is of this type (as in, it is a single item), then
            create a single series. This is for consistency so that every
            possible plotting attribute can be used in a "groupby"

        Returns
        -------
        grouper : pandas.Series
            A mapping of the high dimensional data samples to the attribute
        """
        if dtype is None or isinstance(attribute, dtype):
            # Use this single attribute for everything
            return pd.Series([attribute]*self.n_samples, index=self.samples)
        else:
            order = sns.utils.categorical_order(attribute, order)
            attribute = pd.Categorical(attribute, categories=order,
                                       ordered=True)
            return pd.Series(attribute, index=self.samples)

    def _color_grouper_from_palette(self, grouped, palette, hue_order):
        color_tuples = [zip(df.index, [palette[i]] * df.shape[0])
                        for i, (g, df) in enumerate(grouped)]

        colors = dict(itertools.chain(*color_tuples))
        color_groupby = self._maybe_make_grouper(colors, hue_order)
        return color_groupby

    def establish_symbols(self, marker, marker_order, text, text_order,
                          linewidth, linewidth_order, edgecolor,
                          edgecolor_order):
        """Figure out what to put on the axes for each sample in the data"""
        if isinstance(text, bool):
            # Option 1: Text is a boolean
            if text:
                # 1a: text=True, so use the sample names of data as the
                # plotting symbol
                symbol = pd.Series(
                    map(str, self.samples), index=self.samples)
            else:
                # 1b: text=False, so use the specified marker for each sample
                symbol = self._maybe_make_grouper(marker, marker_order, str)

        else:
            # Assume "text" is a mapping from row names (sample ids) of the
            # data to text labels
            text_order = sns.utils.categorical_order(text, text_order)
            symbol = pd.Series(pd.Categorical(text, categories=text_order,
                                              ordered=True),
                               index=self.samples)
            if marker is not None:
                warnings.warn('Overriding plotting symbol from "marker" with '
                              'values in "text"')

            # Turn text into a boolean
            text = True

        self.symbol = symbol

        # Set these even if text=True because they won't be used when plotting
        # the text anyways
        self.linewidth = self._maybe_make_grouper(linewidth, linewidth_order,
                                                  (int, float))
        self.edgecolor = self._maybe_make_grouper(edgecolor, edgecolor_order,
                                                  str)
        self.text = text

    def symbolplotter(self, xs, ys, symbol, linewidth, edgecolor, **kwargs):
        """Plots either a matplotlib marker or a string at each data position

        Wraps plt.text and plt.plot

        Parameters
        ----------
        xs : array-like
            List of x positions for data
        ys : array-like
            List of y-positions for data
        symbol : str
            What to plot at each (x, y) data position
        text : bool
            If true, then "symboL" is assumed to be a string and iterates over
            each data point individually, using plt.text to position the text.
            Otherwise, "symbol" is a matplotlib marker and uses plt.plot for
            plotting
        kwargs
            Any other keyword arguments to plt.text or plt.plot
        """
        # If both the x- and y- positions don't have data, don't do anything
        if xs.empty and ys.empty:
            return

        if self.text:
            # Plot each (x, y) position as text
            for x, y in zip(xs, ys):
                plt.text(x, y, symbol, **kwargs)
        else:
            # use plt.plot instead of plt.scatter for speed, since plotting all
            # the same marker shape and color and linestyle
            plt.plot(xs, ys, 'o', marker=symbol, linewidth=linewidth,
                     edgecolor=edgecolor, **kwargs)

    def annotate_axes(self, ax):
        """Add descriptive labels to an Axes object."""
        if self.orient == "v":
            xlabel, ylabel = self.group_label, self.value_label
        else:
            xlabel, ylabel = self.value_label, self.group_label

        if xlabel is not None:
            ax.set_xlabel(xlabel)
        if ylabel is not None:
            ax.set_ylabel(ylabel)

        if self.orient == "v":
            ax.set_xticks(np.arange(len(self.reduced_data)))
            ax.set_xticklabels(self.group_names)
        else:
            ax.set_yticks(np.arange(len(self.reduced_data)))
            ax.set_yticklabels(self.group_names)

        if self.orient == "v":
            ax.xaxis.grid(False)
            ax.set_xlim(-.5, len(self.reduced_data) - .5)
        else:
            ax.yaxis.grid(False)
            ax.set_ylim(-.5, len(self.reduced_data) - .5)

        if self.hue_names is not None:
            leg = ax.legend(loc="best")
            if self.hue_title is not None:
                leg.set_title(self.hue_title)

                # Set the title size a roundabout way to maintain
                # compatability with matplotlib 1.1
                try:
                    title_size = mpl.rcParams["axes.labelsize"] * .85
                except TypeError:  # labelsize is something like "large"
                    title_size = mpl.rcParams["axes.labelsize"]
                prop = mpl.font_manager.FontProperties(size=title_size)
                leg._legend_title_box._text.set_font_properties(prop)

    def add_legend_data(self, ax, color, label):
        """Add a dummy patch object so we can get legend data."""
        rect = plt.Rectangle([0, 0], 0, 0,
                             linewidth=self.linewidth / 2,
                             edgecolor=self.gray,
                             facecolor=color,
                             label=label)
        ax.add_patch(rect)

    def draw_markers(self, ax, x=0, y=1, **kwargs):
        """Plot each sample in the data in the reduced space"""

        # Iterate over all the possible modifications of the points
        for color, color_df in self.reduced_data.groupby(self.color):
            for symbol, symbol_df in color_df.groupby(self.symbol):
                for lw, lw_df in symbol_df.groupby(self.linewidth):
                    for ec, ec_df in lw_df.groupby(self.edgecolor):
                        # and finally ... actually plot the data!
                        self.symbolplotter(ec_df[:, x], ec_df[:, y],
                                           symbol=symbol, color=color,
                                           ax=ax, linewidth=lw, edgecolor=ec,
                                           **kwargs)

    def draw_images(self, ax, images):
        if hasattr(offsetbox, 'AnnotationBbox'):
            # only print thumbnails with matplotlib > 1.0
            shown_images = np.array([[1., 1.]])  # just something big
            for i in range(self.reduced_data.shape[0]):
                dist = np.sum((self.reduced_data[i] - shown_images) ** 2, 1)
                if np.min(dist) < 5e-4 * np.product(images[0].shape):
                    # don't show points that are too close
                    continue
                shown_images = np.r_[shown_images, [self.reduced_data[i]]]
                imagebox = offsetbox.AnnotationBbox(
                    offsetbox.OffsetImage(images[i], cmap=plt.cm.gray),
                    self.reduced_data[i])
                ax.add_artist(imagebox)

    def compute_reduction(self):
        """Use reducer to smush high-dimensional data into a smaller space"""
        matrix = self.reducer.fit_transform(self.high_dimensional_data)
        columns = np.arange(matrix.shape[1], dtype=int) + 1
        self.reduced_data = pd.DataFrame(
            matrix, columns=columns, index=self.high_dimensional_data.index)

_reducer_docs = dict(
    input_params=dedent("""
    data : DataFrame, array, or list of arrays
        Dataset to reduce dimensionality and plot.
        """),
    component_input_params=dedent("""\
    x, y : int
        1-based integer of components to plot on "x" and "y" axes. Defaults
        are ``x=1``, ``y=2``.\
        """),

    groupby_input_params=dedent("""\
    hue, marker : mapping (dict or pandas.Series)
        A dict or pandas.Series mapping the row names of ``data`` to a
        separate category to plot as separate colors (hue) or plotting
        symbols (marker). See examples for interpretation.\
        """),

    # From seaborn.categorical
    color=dedent("""\
    color : matplotlib color, optional
        Color for all of the elements, or seed for :func:`light_palette` when
        using hue nesting.\
    """),
    palette=dedent("""\
    palette : palette name, list, or dict, optional
        Color palette that maps either the grouping variable or the hue
        variable. If the palette is a dictionary, keys should be names of
        levels and values should be matplotlib colors.\
    """),
    saturation=dedent("""\
    saturation : float, optional
        Proportion of the original saturation to draw colors at. Large patches
        often look better with slightly desaturated colors, but set this to
        ``1`` if you want the plot colors to perfectly match the input color
        spec.\
    """),
    ax_in=dedent("""\
    ax : matplotlib Axes, optional
        Axes object to draw the plot onto, otherwise uses the current Axes.\
    """),
)
<|endoftext|>


<commit_before>from jinja2 import FileSystemLoader
from jinja2.environment import Environment
import smtplib
from email.mime.text import MIMEText
import time
import json

class EmailTools():

	def __init__(self, config):
		self.env = Environment()
		self.env.loader = FileSystemLoader('templates/email')

	def send_email(self, to, subject, body):
		msg = MIMEText(body)
		msg['Subject'] = subject
		msg['From'] = "auth@" + config["auth"]["domain"]
		msg['To'] = to
		msg['Precedence'] = "bulk"
		msg['Auto-Submitted'] = 'auto-generated'
		s = smtplib.SMTP('localhost')
		s.sendmail(msg['From'], msg['To'], msg.as_string())
		s.quit()

	def render_email(self, to, subject, template, **kwargs):
		template = self.env.get_template(template)
		print template.render(**kwargs)
		self.send_email(to, subject, template.render(**kwargs))

<commit_msg>Fixed the email module to work with global config values<commit_after>from jinja2 import FileSystemLoader
from jinja2.environment import Environment
import smtplib
from email.mime.text import MIMEText
import time
import json

class EmailTools():

	def __init__(self, config):
		self.config = config
		self.env = Environment()
		self.env.loader = FileSystemLoader('templates/email')

	def send_email(self, to, subject, body):
		msg = MIMEText(body)
		msg['Subject'] = subject
		msg['From'] = "auth@" + self.config["auth"]["domain"]
		msg['To'] = to
		msg['Precedence'] = "bulk"
		msg['Auto-Submitted'] = 'auto-generated'
		s = smtplib.SMTP('localhost')
		s.sendmail(msg['From'], msg['To'], msg.as_string())
		s.quit()

	def render_email(self, to, subject, template, **kwargs):
		template = self.env.get_template(template)
		print template.render(**kwargs)
		self.send_email(to, subject, template.render(**kwargs))

<|endoftext|>


<commit_before>from collections import namedtuple, UserList
from enum import Enum, unique
from random import randint, sample, shuffle

@unique
class Suit(Enum):
    JOKER = 0
    CLUB = 1
    SPADE = 2
    DIAMOND = 3
    HEART = 4

@unique
class Value(Enum):
    JOKER = 0
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 4
    FIVE = 5
    SIX = 6
    SEVEN = 7
    EIGHT = 8
    NINE = 9
    TEN = 10
    JACK = 11
    QUEEN = 12
    KING = 13

standard_suits = [suit for suit in Suit if suit != Suit.JOKER]
standard_values = [value for value in Value if value != Value.JOKER]

CardTuple = namedtuple('Card', ['Suit', 'Value'])
class Card(CardTuple):
    def __str__(self):
        return "" + str(self.Suit) + "|" + str(self.Value)

class Deck(UserList):
    def __init__(self, cards=[]):
        self.data = cards

    def new_pack(self, jokers=0):
        '''Fill the Deck with a new, complete set of playing card with the
        number of jokers desired'''
        self.clear()

        for _ in range(jokers):
            self.data.append(Card(Suit.JOKER, Value.JOKER))

        for suit in standard_suits:
            for value in standard_values:
                self.data.append(Card(suit, value))

    def found_pack(self):
        '''I found this abandoned pack of playing cards at the library. It's
        probably not complete... But what if it is...? Oh, wow. Who cares? Look
        at the design on the back! I'm keeping these.'''
        self.clear() #Empty the Deck; must be first line of function

        full_deck = Deck()
        full_deck.new_pack(jokers=randint(0,2))

        self.data = sample(full_deck, randint(0,len(full_deck)))

    def draw(self):
        '''Draw a card from the top of the Deck. Alias for list.pop().'''
        return self.pop()

    def shuffle(self):
        '''Shuffle the Deck.'''
        shuffle(self.data)

    def cut(self, weights):
        '''Cut the deck, return the cuts as list elements. Each slice is given an
        integer weight, in order, from the list `weights`.  The slices are
        returned in the order of their weight. This means that a simple [1,0,2]
        will work, as will [2,1,3], as will [0, -765, 100000].'''
        #https://stackoverflow.com/a/7851166
        positions = sorted(range(len(weights)), key=lambda k: weights[k])

        cut_size = len(self.data) // len(weights)

        deck_cuts = []
        for p in positions:
            deck_cuts.append(self.data[cut_size*p:cut_size*(p+1)])

        return deck_cuts

    #FIXME: Doesn't detect if there are more weights than cards.
    #FIXME: Might have a bug where it loses a card if the slices are not even.
    #For example, four cards and three slices. Testing needed.
    def cut_shuffle(self, weights):
        new_deck = []
        for cuts in self.cut(weights):
            new_deck += cuts #self.data[cut_size*p:cut_size*(p+1)]

        self.data = new_deck

if __name__ == '__main__':
    d = Deck()
    d.new_pack()
    d.shuffle()

    for card in d:
        print(card)
<commit_msg>Draw more than one card, get a list<commit_after>from collections import namedtuple, UserList
from enum import Enum, unique
from random import randint, sample, shuffle

@unique
class Suit(Enum):
    JOKER = 0
    CLUB = 1
    SPADE = 2
    DIAMOND = 3
    HEART = 4

@unique
class Value(Enum):
    JOKER = 0
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 4
    FIVE = 5
    SIX = 6
    SEVEN = 7
    EIGHT = 8
    NINE = 9
    TEN = 10
    JACK = 11
    QUEEN = 12
    KING = 13

standard_suits = [suit for suit in Suit if suit != Suit.JOKER]
standard_values = [value for value in Value if value != Value.JOKER]

CardTuple = namedtuple('Card', ['Suit', 'Value'])
class Card(CardTuple):
    def __str__(self):
        return "" + str(self.Suit) + "|" + str(self.Value)

class Deck(UserList):
    def __init__(self, cards=[]):
        self.data = cards

    def new_pack(self, jokers=0):
        '''Fill the Deck with a new, complete set of playing card with the
        number of jokers desired'''
        self.clear() #Empty the Deck; must be first line of function

        for _ in range(jokers):
            self.data.append(Card(Suit.JOKER, Value.JOKER))

        for suit in standard_suits:
            for value in standard_values:
                self.data.append(Card(suit, value))

    def found_pack(self):
        '''I found this abandoned pack of playing cards at the library. It's
        probably not complete... But what if it is...? Oh, wow. Who cares? Look
        at the design on the back! I'm keeping these.'''
        self.clear() #Empty the Deck; must be first line of function

        full_deck = Deck()
        full_deck.new_pack(jokers=randint(0,2))

        self.data = sample(full_deck, randint(0,len(full_deck)))

    def draw(self, count=1):
        '''Draw cards from the top of the Deck. If you draw more than one card,
        the cards are returned in a list.'''
        if count > 1:
            cards = self.data[:count]
            self.data = self.data[count:]

        return self.pop()

    def shuffle(self):
        '''Shuffle the Deck.'''
        shuffle(self.data)

    def cut(self, weights):
        '''Cut the deck, return the cuts as list elements. Each slice is given an
        integer weight, in order, from the list `weights`.  The slices are
        returned in the order of their weight. This means that a simple [1,0,2]
        will work, as will [2,1,3], as will [0, -765, 100000].'''
        #https://stackoverflow.com/a/7851166
        positions = sorted(range(len(weights)), key=lambda k: weights[k])

        cut_size = len(self.data) // len(weights)

        deck_cuts = []
        for p in positions:
            deck_cuts.append(self.data[cut_size*p:cut_size*(p+1)])

        return deck_cuts

    #FIXME: Doesn't detect if there are more weights than cards.
    #FIXME: Might have a bug where it loses a card if the slices are not even.
    #For example, four cards and three slices. Testing needed.
    def cut_shuffle(self, weights):
        '''Shuffle the deck by cutting the cards'''
        new_deck = []
        for cuts in self.cut(weights):
            new_deck += cuts #self.data[cut_size*p:cut_size*(p+1)]

        self.data = new_deck

if __name__ == '__main__':
    d = Deck()
    d.new_pack()
    d.shuffle()

    for card in d:
        print(card)
<|endoftext|>