<commit_before>import logging
from typing import Callable, Optional

import numpy as np

_log = logging.getLogger(__name__)

class IConstraint:
    def evaluate(self, x: np.ndarray) -> np.ndarray:
        """
        :param x: Array of shape (n_points x n_dims) containing input locations to evaluate constraint at
        :return: Numpy array of shape (n_input,) where an element will be 1 if the corresponding input satisfies the
                 constraint and zero if the constraint is violated
        """
        raise NotImplementedError

class InequalityConstraint(IConstraint):
    def __init__(self, lower_bound: np.ndarray, upper_bound: np.ndarray):
        """
        :param lower_bound: Lower bound vector of size (n_constraint,). Can be -np.inf for one sided constraint
        :param upper_bound: Upper bound vector of size (n_constraint,). Can be np.inf for one sided constraint
        """
        if (lower_bound is None) and (upper_bound is None):
            raise ValueError('Neither lower nor upper bounds is set, at least one must be specified')

        # Default lower bound to -infinity
        if lower_bound is None:
            lower_bound = np.full([upper_bound.shape[0]], -np.inf)

        # Default upper bound to +infinity
        if upper_bound is None:
            upper_bound = np.full([lower_bound.shape[0]], np.inf)

        if np.any((lower_bound == -np.inf) & (upper_bound == np.inf)):
            raise ValueError('One or more inequality constraints are unbounded')

        if np.any(lower_bound >= upper_bound):
            raise ValueError('Lower bound is greater than or equal to upper bound for one or more constraints')

        self.lower_bound = lower_bound
        self.upper_bound = upper_bound

class LinearInequalityConstraint(InequalityConstraint):
    """
    Constraint of the form lower_bound < Ax < upper_bound where the matrix A is called "constraint_matrix"
    """
    def __init__(self, constraint_matrix: np.ndarray, lower_bound: np.ndarray=None, upper_bound: np.ndarray=None):
        """

        :param constraint_matrix: (n_constraint, n_x_dims) matrix in b_lower < Ax < b_upper
        :param lower_bound: Lower bound vector of size (n_constraint,). Can be -np.inf for one sided constraint
        :param upper_bound: Upper bound vector of size (n_constraint,). Can be np.inf for one sided constraint
        """
        super().__init__(lower_bound, upper_bound)
        if (constraint_matrix.shape[0] != lower_bound.shape[0]) or (constraint_matrix.shape[0] != upper_bound.shape[0]):
            raise ValueError('Shape mismatch between constraint matrix {} and lower {} or upper {} bounds'.format(
                              constraint_matrix.shape, lower_bound.shape, upper_bound.shape))

        self.constraint_matrix = constraint_matrix

    def evaluate(self, x: np.ndarray) -> np.ndarray:
        """
        Evaluate whether constraints are violated or satisfied at a set of x locations

        :param x: Array of shape (n_points x n_dims) containing input locations to evaluate constraint at
        :return: Numpy array of shape (n_points, ) where an element will be 1 if the corresponding input satisfies all
                 constraints and zero if any constraint is violated
        """
        if self.constraint_matrix.shape[1] != x.shape[1]:
            raise ValueError('Dimension mismatch between constraint matrix (second dim {})' +
                            ' and input x (second dim {})'.format(self.constraint_matrix.shape[1], x.shape[1]))

        # Transpose here is needed to handle input dimensions
        # that is, A is (n_const, n_dims) and x is (n_points, n_dims)
        ax = self.constraint_matrix.dot(x.T).T
        return np.all((ax >= self.lower_bound) & (ax <= self.upper_bound), axis=1)

class NonlinearInequalityConstraint(InequalityConstraint):
    """
    Constraint of the form lower_bound < g(x) < upper_bound
    """
    def __init__(self, constraint_function: Callable, lower_bound: np.ndarray, upper_bound: np.ndarray,
                 jacobian_fun: Optional[Callable]=None):
        """
        :param constraint_function: function defining constraint in b_lower < fun(x) < b_upper.
                                    Has signature f(x) -> array, shape(m,) where x is 1d and m is the number of constraints
        :param lower_bound: Lower bound vector of size (n_constraint,). Can be -np.inf for one sided constraint
        :param upper_bound: Upper bound vector of size (n_constraint,). Can be np.inf for one sided constraint
        :param jacobian_fun: Function returning the jacobian of the constraint function. Optional, if not supplied
                             the optimizer will use finite differences to calculate the gradients of the constraint
        """

        super().__init__(lower_bound, upper_bound)

        self.fun = constraint_function
        self.jacobian_fun = jacobian_fun

    def evaluate(self, x: np.ndarray) -> np.ndarray:
        """
        Evaluate whether constraints are violated or satisfied at a set of x locations

        :param x: Array of shape (n_points x n_dims) containing input locations to evaluate constraint at
        :return: Numpy array of shape (n_input,) where an element will be 1 if the corresponding input satisfies the
                 constraint and zero if the constraint is violated
        """
        fun_x = np.array([self.fun(x) for x in x])
        return np.all([fun_x >= self.lower_bound, fun_x <= self.upper_bound], axis=0)
<commit_msg>Updating inequality constraints docstrings to correctly indicate closed interval (#372)<commit_after>import logging
from typing import Callable, Optional

import numpy as np

_log = logging.getLogger(__name__)

class IConstraint:
    def evaluate(self, x: np.ndarray) -> np.ndarray:
        """
        :param x: Array of shape (n_points x n_dims) containing input locations to evaluate constraint at
        :return: Numpy array of shape (n_input,) where an element will be 1 if the corresponding input satisfies the
                 constraint and zero if the constraint is violated
        """
        raise NotImplementedError

class InequalityConstraint(IConstraint):
    def __init__(self, lower_bound: np.ndarray, upper_bound: np.ndarray):
        """
        :param lower_bound: Lower bound vector of size (n_constraint,). Can be -np.inf for one sided constraint
        :param upper_bound: Upper bound vector of size (n_constraint,). Can be np.inf for one sided constraint
        """
        if (lower_bound is None) and (upper_bound is None):
            raise ValueError('Neither lower nor upper bounds is set, at least one must be specified')

        # Default lower bound to -infinity
        if lower_bound is None:
            lower_bound = np.full([upper_bound.shape[0]], -np.inf)

        # Default upper bound to +infinity
        if upper_bound is None:
            upper_bound = np.full([lower_bound.shape[0]], np.inf)

        if np.any((lower_bound == -np.inf) & (upper_bound == np.inf)):
            raise ValueError('One or more inequality constraints are unbounded')

        if np.any(lower_bound >= upper_bound):
            raise ValueError('Lower bound is greater than or equal to upper bound for one or more constraints')

        self.lower_bound = lower_bound
        self.upper_bound = upper_bound

class LinearInequalityConstraint(InequalityConstraint):
    """
    Constraint of the form lower_bound <= Ax <= upper_bound where the matrix A is called "constraint_matrix"
    """
    def __init__(self, constraint_matrix: np.ndarray, lower_bound: np.ndarray=None, upper_bound: np.ndarray=None):
        """

        :param constraint_matrix: (n_constraint, n_x_dims) matrix in b_lower <= Ax <= b_upper
        :param lower_bound: Lower bound vector of size (n_constraint,). Can be -np.inf for one sided constraint
        :param upper_bound: Upper bound vector of size (n_constraint,). Can be np.inf for one sided constraint
        """
        super().__init__(lower_bound, upper_bound)
        if (constraint_matrix.shape[0] != lower_bound.shape[0]) or (constraint_matrix.shape[0] != upper_bound.shape[0]):
            raise ValueError('Shape mismatch between constraint matrix {} and lower {} or upper {} bounds'.format(
                              constraint_matrix.shape, lower_bound.shape, upper_bound.shape))

        self.constraint_matrix = constraint_matrix

    def evaluate(self, x: np.ndarray) -> np.ndarray:
        """
        Evaluate whether constraints are violated or satisfied at a set of x locations

        :param x: Array of shape (n_points x n_dims) containing input locations to evaluate constraint at
        :return: Numpy array of shape (n_points, ) where an element will be 1 if the corresponding input satisfies all
                 constraints and zero if any constraint is violated
        """
        if self.constraint_matrix.shape[1] != x.shape[1]:
            raise ValueError('Dimension mismatch between constraint matrix (second dim {})' +
                            ' and input x (second dim {})'.format(self.constraint_matrix.shape[1], x.shape[1]))

        # Transpose here is needed to handle input dimensions
        # that is, A is (n_const, n_dims) and x is (n_points, n_dims)
        ax = self.constraint_matrix.dot(x.T).T
        return np.all((ax >= self.lower_bound) & (ax <= self.upper_bound), axis=1)

class NonlinearInequalityConstraint(InequalityConstraint):
    """
    Constraint of the form lower_bound <= g(x) <= upper_bound
    """
    def __init__(self, constraint_function: Callable, lower_bound: np.ndarray, upper_bound: np.ndarray,
                 jacobian_fun: Optional[Callable]=None):
        """
        :param constraint_function: function defining constraint in b_lower <= fun(x) <= b_upper.
                                    Has signature f(x) -> array, shape(m,) where x is 1d and m is the number of constraints
        :param lower_bound: Lower bound vector of size (n_constraint,). Can be -np.inf for one sided constraint
        :param upper_bound: Upper bound vector of size (n_constraint,). Can be np.inf for one sided constraint
        :param jacobian_fun: Function returning the jacobian of the constraint function. Optional, if not supplied
                             the optimizer will use finite differences to calculate the gradients of the constraint
        """

        super().__init__(lower_bound, upper_bound)

        self.fun = constraint_function
        self.jacobian_fun = jacobian_fun

    def evaluate(self, x: np.ndarray) -> np.ndarray:
        """
        Evaluate whether constraints are violated or satisfied at a set of x locations

        :param x: Array of shape (n_points x n_dims) containing input locations to evaluate constraint at
        :return: Numpy array of shape (n_input,) where an element will be 1 if the corresponding input satisfies the
                 constraint and zero if the constraint is violated
        """
        fun_x = np.array([self.fun(x) for x in x])
        return np.all([fun_x >= self.lower_bound, fun_x <= self.upper_bound], axis=0)
<|endoftext|>


<commit_before>'''
prepare-imdb.py

description: prepare the imdb data for training in DNNs
'''

import cPickle as pickle
import os
import glob
import logging
from multiprocessing import Pool

LOGGER_PREFIX = ' %s'
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log(msg, logger=logger):
    logger.info(LOGGER_PREFIX % msg)

import numpy as np

log('Importing spaCy...')
from spacy.en import English

from cnn.wordvectors.glove import GloVeBox
from util.misc import normalize_sos

log('Initializing spaCy...')
nlp = English()

# -- path where the download script downloads to
DATA_PREFIX = './datasets/aclImdb/aclImdb'
DOWNLOAD_PATH = './datasets/aclImdb'
WV_FILE = './data/wv/IMDB-GloVe-100dim.txt'

def parallel_run(f, parms):
    '''
    performs multi-core map of the function `f`
    over the parameter space spanned by parms.

    `f` MUST take only one argument.
    '''
    pool = Pool()
    ret = pool.map(f, parms)
    pool.close()
    pool.join()
    return ret

def data_integrity():
	all_ok = False
	if os.path.isdir(DATA_PREFIX):
		for part in ['train', 'test']:
			for lab in ['pos', 'neg']:
				if not os.path.isdir(os.path.join(DATA_PREFIX, which, 'pos')):
					all_ok = False
					break
			if not all_ok:
				break
	if not all_ok:
		wkdir = os.getcwd()
		os.chdir(DOWNLOAD_PATH)
		import subprocess
		subprocess.call("./download.sh", shell=True)
		os.chdir(wkdir)

def get_data(positive=True, which='train'):
	_logger = logging.getLogger(__name__)
	if positive:
		examples = glob.glob(os.path.join(DATA_PREFIX, which, 'pos', '*.txt'))
	else:
		examples = glob.glob(os.path.join(DATA_PREFIX, which, 'neg', '*.txt'))
	data = []
	for i, f in enumerate(examples):
		if (i + 1) % 1000 == 0:
			log('Reading: {} of {}'.format(i + 1, len(examples)), _logger)
		data.append((open(f, 'rb').read().lower()).replace('

', '\n'))
	return data

def parse_paragraph(txt):
	'''
	Takes a text and returns a list of lists of tokens, where each sublist is a sentence
	'''
	return [[t.text for t in s] for s in nlp(u'' + txt.decode('ascii',errors='ignore')).sents]

def parse_tokens(txt):
	'''
	Takes a text and returns a list of tokens
	'''
	return [tx for tx in (t.text for t in nlp(u'' + txt.decode('ascii',errors='ignore'))) if tx != '\n']

if __name__ == '__main__':

	log('Checking data integrity...')

	data_integrity()

	log('Building word vectors from {}'.format(WV_FILE))
	gb = GloVeBox(WV_FILE)
	gb.build(zero_token=True).index()

	log('writing GloVeBox pickle...')
	pickle.dump(gb, open(WV_FILE.replace('.txt', '-glovebox.pkl'), 'wb'), pickle.HIGHEST_PROTOCOL)

	log('Getting training examples')
	train_neg = get_data(positive=False)
	train_pos = get_data()

	train, test = {}, {}

	log('Splitting training data into paragraphs')
	# tok_neg, tok_pos = parallel_run(parse_tokens, train_neg), parallel_run(parse_tokens, train_pos)
	train['paragraph_neg'], train['paragraph_pos'] = parallel_run(parse_paragraph, train_neg), parallel_run(parse_paragraph, train_pos)

	log('Getting testing examples')
	test_neg = get_data(positive=False, which='test')
	test_pos = get_data(which='test')

	log('Splitting testing data into paragraphs')
	# tok_neg_test, tok_pos_test = parallel_run(parse_tokens, test_neg), parallel_run(parse_tokens, test_pos)
	test['paragraph_neg'], test['paragraph_pos'] = parallel_run(parse_paragraph, test_neg), parallel_run(parse_paragraph, test_pos)

	# -- parameters to tune and set
	WORDS_PER_SENTENCE = 50
	SENTENCES_PER_PARAGRAPH = 50
	PREPEND = True

	log('normalizing training inputs...')
	train_repr = normalize_sos(
							[
								normalize_sos(review, WORDS_PER_SENTENCE, prepend=PREPEND)
								for review in gb.get_indices(train['paragraph_pos'] + train['paragraph_neg'])
							],
			SENTENCES_PER_PARAGRAPH, [0] * WORDS_PER_SENTENCE, PREPEND
		)

	train_text = np.array(train_repr)
	train_labels = np.array([1] * len(train['paragraph_pos']) + [0] * len(train['paragraph_pos'])).astype('float32')

	log('normalizing testing inputs...')
	test_repr = normalize_sos(
						[
							normalize_sos(review, WORDS_PER_SENTENCE, prepend=PREPEND)
							for review in gb.get_indices(test['paragraph_pos'] + test['paragraph_neg'])
						],
		SENTENCES_PER_PARAGRAPH, [0] * WORDS_PER_SENTENCE, PREPEND
	)

	test_text = np.array(test_repr)
	test_labels = np.array([1] * len(test['paragraph_pos']) + [0] * len(test['paragraph_pos'])).astype('float32')
	log('Saving...')

	# -- training data save
	np.save('IMDB_train_glove_X.npy', train_text)
	np.save('IMDB_train_glove_y.npy', train_labels)

	# -- testing data save
	np.save('IMDB_test_glove_X.npy', test_text)
	np.save('IMDB_test_glove_y.npy', test_labels)

<commit_msg>refinements for submission to cluster<commit_after>'''
prepare-imdb.py

description: prepare the imdb data for training in DNNs
'''

import cPickle as pickle
import os
import glob
import logging
from multiprocessing import Pool

LOGGER_PREFIX = ' %s'
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def log(msg, logger=logger):
    logger.info(LOGGER_PREFIX % msg)

import numpy as np

log('Importing spaCy...')
from spacy.en import English

from cnn.wordvectors.glove import GloVeBox
from util.misc import normalize_sos

log('Initializing spaCy...')
nlp = English()

# -- path where the download script downloads to
DATA_PREFIX = './datasets/aclImdb/aclImdb'
DOWNLOAD_PATH = './datasets/aclImdb'
WV_FILE = './data/wv/IMDB-GloVe-100dim.txt'

def parallel_run(f, parms):
    '''
    performs multi-core map of the function `f`
    over the parameter space spanned by parms.

    `f` MUST take only one argument.
    '''
    pool = Pool()
    ret = pool.map(f, parms)
    pool.close()
    pool.join()
    return ret

def data_integrity():
	all_ok = False
	if os.path.isdir(DATA_PREFIX):
		for part in ['train', 'test']:
			for lab in ['pos', 'neg']:
				if not os.path.isdir(os.path.join(DATA_PREFIX, part, lab)):
					all_ok = False
					break
			if not all_ok:
				break
	if not all_ok:
		wkdir = os.getcwd()
		os.chdir(DOWNLOAD_PATH)
		import subprocess
		subprocess.call("./download.sh", shell=True)
		os.chdir(wkdir)

def get_data(positive=True, which='train'):
	_logger = logging.getLogger(__name__)
	if positive:
		examples = glob.glob(os.path.join(DATA_PREFIX, which, 'pos', '*.txt'))
	else:
		examples = glob.glob(os.path.join(DATA_PREFIX, which, 'neg', '*.txt'))
	data = []
	for i, f in enumerate(examples):
		if (i + 1) % 1000 == 0:
			log('Reading: {} of {}'.format(i + 1, len(examples)), _logger)
		data.append((open(f, 'rb').read().lower()).replace('

', '\n'))
	return data

def parse_paragraph(txt):
	'''
	Takes a text and returns a list of lists of tokens, where each sublist is a sentence
	'''
	return [[t.text for t in s] for s in nlp(u'' + txt.decode('ascii',errors='ignore')).sents]

def parse_tokens(txt):
	'''
	Takes a text and returns a list of tokens
	'''
	return [tx for tx in (t.text for t in nlp(u'' + txt.decode('ascii',errors='ignore'))) if tx != '\n']

if __name__ == '__main__':

	log('Checking data integrity...')

	data_integrity()

	log('Building word vectors from {}'.format(WV_FILE))
	gb = GloVeBox(WV_FILE)
	gb.build(zero_token=True).index()

	log('writing GloVeBox pickle...')
	pickle.dump(gb, open(WV_FILE.replace('.txt', '-glovebox.pkl'), 'wb'), pickle.HIGHEST_PROTOCOL)

	log('Getting training examples')
	train_neg = get_data(positive=False)
	train_pos = get_data()

	train, test = {}, {}

	log('Splitting training data into paragraphs')
	# tok_neg, tok_pos = parallel_run(parse_tokens, train_neg), parallel_run(parse_tokens, train_pos)
	train['paragraph_neg'], train['paragraph_pos'] = parallel_run(parse_paragraph, train_neg), parallel_run(parse_paragraph, train_pos)

	log('Getting testing examples')
	test_neg = get_data(positive=False, which='test')
	test_pos = get_data(which='test')

	log('Splitting testing data into paragraphs')
	# tok_neg_test, tok_pos_test = parallel_run(parse_tokens, test_neg), parallel_run(parse_tokens, test_pos)
	test['paragraph_neg'], test['paragraph_pos'] = parallel_run(parse_paragraph, test_neg), parallel_run(parse_paragraph, test_pos)

	# -- parameters to tune and set
	WORDS_PER_SENTENCE = 50
	SENTENCES_PER_PARAGRAPH = 50
	PREPEND = True

	log('normalizing training inputs...')
	train_repr = normalize_sos(
							[
								normalize_sos(review, WORDS_PER_SENTENCE, prepend=PREPEND)
								for review in gb.get_indices(train['paragraph_pos'] + train['paragraph_neg'])
							],
			SENTENCES_PER_PARAGRAPH, [0] * WORDS_PER_SENTENCE, PREPEND
		)

	train_text = np.array(train_repr)
	train_labels = np.array([1] * len(train['paragraph_pos']) + [0] * len(train['paragraph_pos'])).astype('float32')

	log('normalizing testing inputs...')
	test_repr = normalize_sos(
						[
							normalize_sos(review, WORDS_PER_SENTENCE, prepend=PREPEND)
							for review in gb.get_indices(test['paragraph_pos'] + test['paragraph_neg'])
						],
		SENTENCES_PER_PARAGRAPH, [0] * WORDS_PER_SENTENCE, PREPEND
	)

	test_text = np.array(test_repr)
	test_labels = np.array([1] * len(test['paragraph_pos']) + [0] * len(test['paragraph_pos'])).astype('float32')
	log('Saving...')

	# -- training data save
	np.save('IMDB_train_glove_X.npy', train_text)
	np.save('IMDB_train_glove_y.npy', train_labels)

	# -- testing data save
	np.save('IMDB_test_glove_X.npy', test_text)
	np.save('IMDB_test_glove_y.npy', test_labels)

<|endoftext|>


<commit_before># TODO: Load the csv files into dataframes here.

class data_preparer():
    def __init__():

<commit_msg>feat: Use 'pandas.read_csv' method to create dataframe<commit_after># TODO: Load the csv files into dataframes here.
import csv
import pandas

class data_preparer():
    def __init__(self):
        pass
    def load(self, filename):
        # X = pandas.DataFrame()
        # with open(filename) as csvfile:
        #     filereader = csv.reader(csvfile,delimiter=',')
        X = pandas.read_csv(filename)
        return X
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
from django.db import models
from django.db.models import fields
from django.db.models.fields import related
from django.utils.datastructures import SortedDict
from autofixture import constraints, generators, signals

class CreateInstanceError(Exception):
    pass

class Data(object):
    '''
    Emulates behaviour of ``_value`` but can hold additional data.

    I'm happy to use an already existing implementation that is out there. But
    for now this serves very well.
    '''
    def __init__(self, *args, **kwargs):
        self._value__ = args.pop(0)
        self._list = args
        self._dict = kwargs

    def __getattr__(self, attr):
        return getattr(self._value__, attr)

class Link(object):
    '''
    Handles logic of following or generating foreignkeys and m2m relations.
    '''
    def __init__(self, fields=None, default=None):
        self.fields = {}
        self.subfields = {}
        self.default = default

        fields = fields or {}
        if fields is True:
            fields = {'ALL': None}
        if not isinstance(fields, dict):
            fields = dict([(v, None) for v in fields])
        for field, value in fields.items():
            try:
                fieldname, subfield = field.split('__', 1)
                self.subfields.setdefault(fieldname, {})[subfield] = value
            except ValueError:
                self.fields[field] = value

    def __getitem__(self, key):
        return self.fields.get(key,
            self.fields.get('ALL', self.default))

    def __iter__(self):
        for field in self.fields:
            yield field
        for key, value in self.subfields.items():
            yield '%s__%s' % (key, value)

    def __contains__(self, value):
        if 'ALL' in self.fields:
            return True
        if value in self.fields:
            return True
        return False

    def get_deep_links(self, field):
        if 'ALL' in self.fields:
            fields = {'ALL': self.fields['ALL']}
        else:
            fields = self.subfields.get(field, {})
            if 'ALL' in fields:
                fields = {'ALL': fields['ALL']}
        return Link(fields, default=self.default)

class AutoFixture(object):
    '''
    We don't support the following fields yet:

        * ``XMLField``
        * ``FileField``
        * ``ImageField``

    Patches are welcome.
    '''
    class IGNORE_FIELD(object):
        pass

    overwrite_defaults = False
    follow_fk = True
    generate_fk = False
    follow_m2m = {'ALL': (1,5)}
    generate_m2m = False

    none_chance = 0.2
    tries = 1000

    field_to_generator = SortedDict((
        (fields.BooleanField, generators.BooleanGenerator),
        (fields.NullBooleanField, generators.NullBooleanGenerator),
        (fields.DateTimeField, generators.DateTimeGenerator),
        (fields.DateField, generators.DateGenerator),
        (fields.PositiveSmallIntegerField, generators.PositiveSmallIntegerGenerator),
        (fields.PositiveIntegerField, generators.PositiveIntegerGenerator),
        (fields.SmallIntegerField, generators.SmallIntegerGenerator),
        (fields.IntegerField, generators.IntegerGenerator),
        (fields.IPAddressField, generators.IPAddressGenerator),
        (fields.TextField, generators.LoremGenerator),
        (fields.TimeField, generators.TimeGenerator),
    ))

    field_values = {}

    default_constraints = [
        constraints.unique_constraint,
        constraints.unique_together_constraint]

    def __init__(self, model,
            field_values=None, none_chance=None, overwrite_defaults=None,
            constraints=None, follow_fk=None, generate_fk=None,
            follow_m2m=None, generate_m2m=None):
        '''
        Parameters:
            ``model``:

            ``field_values``: A dictionary with field names of ``model`` as
                keys. Values may be static values that are assigned to the
                field or a ``Generator`` instance that generates a value on the
                fly.

            ``none_chance``: The chance (between 0 and 1, 1 equals 100%) to
            assign ``None`` to nullable fields.

            ``overwrite_defaults``: All default values of fields are preserved
                by default. If set to ``True``, default values will be
                generated by a matching ``Generator``.

            ``constraints``: A list of callables. The constraints are used to
                verify if the created model instance may be used. The callable
                gets the actual model as first and the instance as second
                parameter. The instance is not populated yet at this moment.
                The callable may raise an ``InvalidConstraint`` exception to
                indicate which fields violate the constraint.

            ``follow_fk``: A boolean value indicating if foreign keys should be
                set to random, already existing, instances of the related
                model. Default is ``True``.

            ``generate_fk``: A boolean which indicates if related models should
                also be created with random values. Default is ``False``. The
                ``follow_fk`` parameter will be ignored if this one is set to
                ``True``.

            ``follow_m2m``: A tuple containing minium and maximum of model
                instances that are assigned to ``ManyToManyField``. No new
                instances will be created. Default is (1, 5).
                You can ignore ``ManyToManyField`` fields by setting this
                parameter to ``False``.

            ``generate_m2m``: A tuple containing minimum and maximum number of
                model instance that are newly created and assigned to the
                ``ManyToManyField``. Default is ``False`` which disables the
                generation of new related instances. The value of
                ``follow_m2m`` will be ignored if this parameter is set.
        '''
        self.model = model
        self.field_values = self.__class__.field_values.copy()
        self.field_values.update(field_values or {})
        self.constraints = constraints or []
        if none_chance is not None:
            self.none_chance = none_chance
        if overwrite_defaults is not None:
            self.overwrite_defaults = overwrite_defaults

        if follow_fk is not None:
            self.follow_fk = follow_fk
        if not isinstance(self.follow_fk, Link):
            self.follow_fk = Link(self.follow_fk)

        if generate_fk is not None:
            self.generate_fk = generate_fk
        if not isinstance(self.generate_fk, Link):
            self.generate_fk = Link(self.generate_fk)

        if follow_m2m is not None:
            if not isinstance(follow_m2m, dict):
                if follow_m2m:
                    follow_m2m = Link({'ALL': follow_m2m})
                else:
                    follow_m2m = Link(False)
            self.follow_m2m = follow_m2m
        if not isinstance(self.follow_m2m, Link):
            self.follow_m2m = Link(self.follow_m2m)

        if generate_m2m is not None:
            if not isinstance(generate_m2m, dict):
                if generate_m2m:
                    generate_m2m = Link({'ALL': generate_m2m})
                else:
                    generate_m2m = Link(False)
            self.generate_m2m = generate_m2m
        if not isinstance(self.generate_m2m, Link):
            self.generate_m2m = Link(self.generate_m2m)

        for constraint in self.default_constraints:
            self.add_constraint(constraint)

        self._field_generators = {}

        self.prepare_class()

    def prepare_class(self):
        pass

    def add_constraint(self, constraint):
        self.constraints.append(constraint)

    def add_field_value(self, name, value):
        self.field_values[name] = value

    def get_generator(self, field):
        '''
        Return a value generator based on the field instance that is passed to
        this method. This function may return ``None`` which means that the
        specified field will be ignored (e.g. if no matching generator was
        found).
        '''
        if isinstance(field, fields.AutoField):
            return None
        if field.default is not fields.NOT_PROVIDED and \
            not self.overwrite_defaults:
                return None
        kwargs = {}

        if field.name in self.field_values:
            value = self.field_values[field.name]
            if isinstance(value, generators.Generator):
                return value
            elif isinstance(value, AutoFixture):
                return generators.InstanceGenerator(autofixture=value)
            return generators.StaticGenerator(value=value)

        if field.null:
            kwargs['none_chance'] = self.none_chance
        if field.choices:
            return generators.ChoicesGenerator(choices=field.choices, **kwargs)
        if isinstance(field, related.ForeignKey):
            # if generate_fk is set, follow_fk is ignored.
            if field.name in self.generate_fk:
                return generators.InstanceGenerator(
                    AutoFixture(
                        field.rel.to,
                        follow_fk=self.follow_fk.get_deep_links(field.name),
                        generate_fk=self.generate_fk.get_deep_links(field.name)),
                    limit_choices_to=field.rel.limit_choices_to)
            if field.name in self.follow_fk:
                return generators.InstanceSelector(
                    field.rel.to,
                    limit_choices_to=field.rel.limit_choices_to)
            if field.blank or field.null:
                return generators.NoneGenerator()
            raise CreateInstanceError(
                u'Cannot resolve ForeignKey "%s" to "%s". Provide either '
                u'"follow_fk" or "generate_fk" parameters.' % (
                    field.name,
                    '%s.%s' % (
                        field.rel.to._meta.app_label,
                        field.rel.to._meta.object_name,
                    )
            ))
        if isinstance(field, related.ManyToManyField):
            if field.name in self.generate_m2m:
                min_count, max_count = self.generate_m2m[field.name]
                return generators.MultipleInstanceGenerator(
                    AutoFixture(
                        field.rel.to
                    ),
                    limit_choices_to=field.rel.limit_choices_to,
                    min_count=min_count,
                    max_count=max_count,
                    **kwargs)
            if field.name in self.follow_m2m:
                min_count, max_count = self.follow_m2m[field.name]
                return generators.InstanceSelector(
                    field.rel.to,
                    limit_choices_to=field.rel.limit_choices_to,
                    min_count=min_count,
                    max_count=max_count,
                    **kwargs)
            if field.blank or field.null:
                return generators.StaticGenerator([])
            raise CreateInstanceError(
                u'Cannot assign instances of "%s" to ManyToManyField "%s". '
                u'Provide either "follow_m2m" or "generate_m2m" argument.' % (
                    '%s.%s' % (
                        field.rel.to._meta.app_label,
                        field.rel.to._meta.object_name,
                    ),
                    field.name,
            ))
        if isinstance(field, fields.FilePathField):
            return generators.FilePathGenerator(
                path=field.path, match=field.match, recursive=field.recursive,
                max_length=field.max_length, **kwargs)
        if isinstance(field, fields.CharField):
            if isinstance(field, fields.SlugField):
                generator = generators.SlugGenerator
            elif isinstance(field, fields.EmailField):
                return generators.EmailGenerator(
                    max_length=min(field.max_length, 30))
            elif isinstance(field, fields.URLField):
                return generators.URLGenerator(
                    max_length=min(field.max_length, 25))
            elif field.max_length > 15:
                return generators.LoremSentenceGenerator(
                    common=False,
                    max_length=field.max_length)
                generator = generators.StringGenerator
            return generator(max_length=field.max_length)
        if isinstance(field, fields.DecimalField):
            return generators.DecimalGenerator(
                decimal_places=field.decimal_places,
                max_digits=field.max_digits)
        if hasattr(fields, 'BigIntegerField'):
            if isinstance(field, fields.BigIntegerField):
                return generators.IntegerGenerator(
                    min_value=-field.MAX_BIGINT - 1,
                    max_value=field.MAX_BIGINT,
                    **kwargs)
        for field_class, generator in self.field_to_generator.items():
            if isinstance(field, field_class):
                return generator(**kwargs)
        return None

    def get_value(self, field):
        '''
        Return a random value that can be assigned to the passed field
        instance.
        '''
        if field not in self._field_generators:
            self._field_generators[field] = self.get_generator(field)
        generator = self._field_generators[field]
        if generator is None:
            return self.IGNORE_FIELD
        value = generator.get_value()
        return value

    def process_field(self, instance, field):
        value = self.get_value(field)
        if value is self.IGNORE_FIELD:
            return
        setattr(instance, field.name, value)

    def process_m2m(self, instance, field):
        # check django's version number to determine how intermediary models
        # are checked if they are auto created or not.
        from django import VERSION
        auto_created_through_model = False
        through = field.rel.through
        if VERSION < (1,2):
            if through:
                if isinstance(through, basestring):
                    bits = through.split('.')
                    if len(bits) < 2:
                        bits = [instance._meta.app_label] + bits
                    through = models.get_model(*bits)
            else:
                auto_created_through_model = True
        else:
            auto_created_through_model = through._meta.auto_created

        if auto_created_through_model:
            return self.process_field(instance, field)
        # if m2m relation has intermediary model:
        #   * only generate relation if 'generate_m2m' is given
        #   * first generate intermediary model and assign a newly created
        #     related model to the foreignkey
        kwargs = {}
        if field.name in self.generate_m2m:
            # get fk to related model on intermediary model
            related_fks = [fk
                for fk in through._meta.fields
                if isinstance(fk, related.ForeignKey) and \
                    fk.rel.to is field.rel.to]
            self_fks = [fk
                for fk in through._meta.fields
                if isinstance(fk, related.ForeignKey) and \
                    fk.rel.to is self.model]
            assert len(related_fks) == 1
            assert len(self_fks) == 1
            related_fk = related_fks[0]
            self_fk = self_fks[0]
            min_count, max_count = self.generate_m2m[field.name]
            intermediary_model = generators.MultipleInstanceGenerator(
                AutoFixture(
                    through,
                    field_values={
                        self_fk.name: instance,
                        related_fk.name: generators.InstanceGenerator(
                            AutoFixture(field.rel.to))
                    }),
                min_count=min_count,
                max_count=max_count,
                **kwargs).generate()

    def check_constrains(self, instance):
        '''
        Return fieldnames which need recalculation.
        '''
        recalc_fields = []
        for constraint in self.constraints:
            try:
                constraint(self.model, instance)
            except constraints.InvalidConstraint, e:
                recalc_fields.extend(e.fields)
        return recalc_fields

    def post_process_instance(self, instance):
        return instance

    def create_one(self, commit=True):
        tries = self.tries
        instance = self.model()
        process = instance._meta.fields
        while process and tries > 0:
            for field in process:
                self.process_field(instance, field)
            process = self.check_constrains(instance)
            tries -= 1
        if tries == 0:
            raise CreateInstanceError(
                u'Cannot solve constraints for "%s", tried %d times. '
                u'Please check value generators or model constraints. '
                u'At least the following fields are involved: %s' % (
                    '%s.%s' % (
                        self.model._meta.app_label,
                        self.model._meta.object_name),
                    self.tries,
                    ', '.join([field.name for field in process]),
            ))
        if commit:
            instance.save()
            for field in instance._meta.many_to_many:
                self.process_m2m(instance, field)
        signals.instance_created.send(
            sender=self,
            model=self.model,
            instance=instance,
            committed=commit)
        return self.post_process_instance(instance)

    def create(self, count=1, commit=True):
        '''
        Create and return ``count`` model instances.

        May raise ``CreateInstanceError`` if constraints are not satisfied.
        '''
        object_list = []
        for i in xrange(count):
            instance = self.create_one(commit=commit)
            object_list.append(instance)
        return object_list

    def iter(self, count=1, commit=True):
        for i in xrange(count):
            yield self.create_one(commit=commit)

    def __iter__(self):
        yield self.create_one()
<commit_msg>Removing Data class since it was not used.<commit_after># -*- coding: utf-8 -*-
from django.db import models
from django.db.models import fields
from django.db.models.fields import related
from django.utils.datastructures import SortedDict
from autofixture import constraints, generators, signals

class CreateInstanceError(Exception):
    pass

class Link(object):
    '''
    Handles logic of following or generating foreignkeys and m2m relations.
    '''
    def __init__(self, fields=None, default=None):
        self.fields = {}
        self.subfields = {}
        self.default = default

        fields = fields or {}
        if fields is True:
            fields = {'ALL': None}
        if not isinstance(fields, dict):
            fields = dict([(v, None) for v in fields])
        for field, value in fields.items():
            try:
                fieldname, subfield = field.split('__', 1)
                self.subfields.setdefault(fieldname, {})[subfield] = value
            except ValueError:
                self.fields[field] = value

    def __getitem__(self, key):
        return self.fields.get(key,
            self.fields.get('ALL', self.default))

    def __iter__(self):
        for field in self.fields:
            yield field
        for key, value in self.subfields.items():
            yield '%s__%s' % (key, value)

    def __contains__(self, value):
        if 'ALL' in self.fields:
            return True
        if value in self.fields:
            return True
        return False

    def get_deep_links(self, field):
        if 'ALL' in self.fields:
            fields = {'ALL': self.fields['ALL']}
        else:
            fields = self.subfields.get(field, {})
            if 'ALL' in fields:
                fields = {'ALL': fields['ALL']}
        return Link(fields, default=self.default)

class AutoFixture(object):
    '''
    We don't support the following fields yet:

        * ``XMLField``
        * ``FileField``
        * ``ImageField``

    Patches are welcome.
    '''
    class IGNORE_FIELD(object):
        pass

    overwrite_defaults = False
    follow_fk = True
    generate_fk = False
    follow_m2m = {'ALL': (1,5)}
    generate_m2m = False

    none_chance = 0.2
    tries = 1000

    field_to_generator = SortedDict((
        (fields.BooleanField, generators.BooleanGenerator),
        (fields.NullBooleanField, generators.NullBooleanGenerator),
        (fields.DateTimeField, generators.DateTimeGenerator),
        (fields.DateField, generators.DateGenerator),
        (fields.PositiveSmallIntegerField, generators.PositiveSmallIntegerGenerator),
        (fields.PositiveIntegerField, generators.PositiveIntegerGenerator),
        (fields.SmallIntegerField, generators.SmallIntegerGenerator),
        (fields.IntegerField, generators.IntegerGenerator),
        (fields.IPAddressField, generators.IPAddressGenerator),
        (fields.TextField, generators.LoremGenerator),
        (fields.TimeField, generators.TimeGenerator),
    ))

    field_values = {}

    default_constraints = [
        constraints.unique_constraint,
        constraints.unique_together_constraint]

    def __init__(self, model,
            field_values=None, none_chance=None, overwrite_defaults=None,
            constraints=None, follow_fk=None, generate_fk=None,
            follow_m2m=None, generate_m2m=None):
        '''
        Parameters:
            ``model``:

            ``field_values``: A dictionary with field names of ``model`` as
                keys. Values may be static values that are assigned to the
                field or a ``Generator`` instance that generates a value on the
                fly.

            ``none_chance``: The chance (between 0 and 1, 1 equals 100%) to
            assign ``None`` to nullable fields.

            ``overwrite_defaults``: All default values of fields are preserved
                by default. If set to ``True``, default values will be
                generated by a matching ``Generator``.

            ``constraints``: A list of callables. The constraints are used to
                verify if the created model instance may be used. The callable
                gets the actual model as first and the instance as second
                parameter. The instance is not populated yet at this moment.
                The callable may raise an ``InvalidConstraint`` exception to
                indicate which fields violate the constraint.

            ``follow_fk``: A boolean value indicating if foreign keys should be
                set to random, already existing, instances of the related
                model. Default is ``True``.

            ``generate_fk``: A boolean which indicates if related models should
                also be created with random values. Default is ``False``. The
                ``follow_fk`` parameter will be ignored if this one is set to
                ``True``.

            ``follow_m2m``: A tuple containing minium and maximum of model
                instances that are assigned to ``ManyToManyField``. No new
                instances will be created. Default is (1, 5).
                You can ignore ``ManyToManyField`` fields by setting this
                parameter to ``False``.

            ``generate_m2m``: A tuple containing minimum and maximum number of
                model instance that are newly created and assigned to the
                ``ManyToManyField``. Default is ``False`` which disables the
                generation of new related instances. The value of
                ``follow_m2m`` will be ignored if this parameter is set.
        '''
        self.model = model
        self.field_values = self.__class__.field_values.copy()
        self.field_values.update(field_values or {})
        self.constraints = constraints or []
        if none_chance is not None:
            self.none_chance = none_chance
        if overwrite_defaults is not None:
            self.overwrite_defaults = overwrite_defaults

        if follow_fk is not None:
            self.follow_fk = follow_fk
        if not isinstance(self.follow_fk, Link):
            self.follow_fk = Link(self.follow_fk)

        if generate_fk is not None:
            self.generate_fk = generate_fk
        if not isinstance(self.generate_fk, Link):
            self.generate_fk = Link(self.generate_fk)

        if follow_m2m is not None:
            if not isinstance(follow_m2m, dict):
                if follow_m2m:
                    follow_m2m = Link({'ALL': follow_m2m})
                else:
                    follow_m2m = Link(False)
            self.follow_m2m = follow_m2m
        if not isinstance(self.follow_m2m, Link):
            self.follow_m2m = Link(self.follow_m2m)

        if generate_m2m is not None:
            if not isinstance(generate_m2m, dict):
                if generate_m2m:
                    generate_m2m = Link({'ALL': generate_m2m})
                else:
                    generate_m2m = Link(False)
            self.generate_m2m = generate_m2m
        if not isinstance(self.generate_m2m, Link):
            self.generate_m2m = Link(self.generate_m2m)

        for constraint in self.default_constraints:
            self.add_constraint(constraint)

        self._field_generators = {}

        self.prepare_class()

    def prepare_class(self):
        pass

    def add_constraint(self, constraint):
        self.constraints.append(constraint)

    def add_field_value(self, name, value):
        self.field_values[name] = value

    def get_generator(self, field):
        '''
        Return a value generator based on the field instance that is passed to
        this method. This function may return ``None`` which means that the
        specified field will be ignored (e.g. if no matching generator was
        found).
        '''
        if isinstance(field, fields.AutoField):
            return None
        if field.default is not fields.NOT_PROVIDED and \
            not self.overwrite_defaults:
                return None
        kwargs = {}

        if field.name in self.field_values:
            value = self.field_values[field.name]
            if isinstance(value, generators.Generator):
                return value
            elif isinstance(value, AutoFixture):
                return generators.InstanceGenerator(autofixture=value)
            return generators.StaticGenerator(value=value)

        if field.null:
            kwargs['none_chance'] = self.none_chance
        if field.choices:
            return generators.ChoicesGenerator(choices=field.choices, **kwargs)
        if isinstance(field, related.ForeignKey):
            # if generate_fk is set, follow_fk is ignored.
            if field.name in self.generate_fk:
                return generators.InstanceGenerator(
                    AutoFixture(
                        field.rel.to,
                        follow_fk=self.follow_fk.get_deep_links(field.name),
                        generate_fk=self.generate_fk.get_deep_links(field.name)),
                    limit_choices_to=field.rel.limit_choices_to)
            if field.name in self.follow_fk:
                return generators.InstanceSelector(
                    field.rel.to,
                    limit_choices_to=field.rel.limit_choices_to)
            if field.blank or field.null:
                return generators.NoneGenerator()
            raise CreateInstanceError(
                u'Cannot resolve ForeignKey "%s" to "%s". Provide either '
                u'"follow_fk" or "generate_fk" parameters.' % (
                    field.name,
                    '%s.%s' % (
                        field.rel.to._meta.app_label,
                        field.rel.to._meta.object_name,
                    )
            ))
        if isinstance(field, related.ManyToManyField):
            if field.name in self.generate_m2m:
                min_count, max_count = self.generate_m2m[field.name]
                return generators.MultipleInstanceGenerator(
                    AutoFixture(
                        field.rel.to
                    ),
                    limit_choices_to=field.rel.limit_choices_to,
                    min_count=min_count,
                    max_count=max_count,
                    **kwargs)
            if field.name in self.follow_m2m:
                min_count, max_count = self.follow_m2m[field.name]
                return generators.InstanceSelector(
                    field.rel.to,
                    limit_choices_to=field.rel.limit_choices_to,
                    min_count=min_count,
                    max_count=max_count,
                    **kwargs)
            if field.blank or field.null:
                return generators.StaticGenerator([])
            raise CreateInstanceError(
                u'Cannot assign instances of "%s" to ManyToManyField "%s". '
                u'Provide either "follow_m2m" or "generate_m2m" argument.' % (
                    '%s.%s' % (
                        field.rel.to._meta.app_label,
                        field.rel.to._meta.object_name,
                    ),
                    field.name,
            ))
        if isinstance(field, fields.FilePathField):
            return generators.FilePathGenerator(
                path=field.path, match=field.match, recursive=field.recursive,
                max_length=field.max_length, **kwargs)
        if isinstance(field, fields.CharField):
            if isinstance(field, fields.SlugField):
                generator = generators.SlugGenerator
            elif isinstance(field, fields.EmailField):
                return generators.EmailGenerator(
                    max_length=min(field.max_length, 30))
            elif isinstance(field, fields.URLField):
                return generators.URLGenerator(
                    max_length=min(field.max_length, 25))
            elif field.max_length > 15:
                return generators.LoremSentenceGenerator(
                    common=False,
                    max_length=field.max_length)
                generator = generators.StringGenerator
            return generator(max_length=field.max_length)
        if isinstance(field, fields.DecimalField):
            return generators.DecimalGenerator(
                decimal_places=field.decimal_places,
                max_digits=field.max_digits)
        if hasattr(fields, 'BigIntegerField'):
            if isinstance(field, fields.BigIntegerField):
                return generators.IntegerGenerator(
                    min_value=-field.MAX_BIGINT - 1,
                    max_value=field.MAX_BIGINT,
                    **kwargs)
        for field_class, generator in self.field_to_generator.items():
            if isinstance(field, field_class):
                return generator(**kwargs)
        return None

    def get_value(self, field):
        '''
        Return a random value that can be assigned to the passed field
        instance.
        '''
        if field not in self._field_generators:
            self._field_generators[field] = self.get_generator(field)
        generator = self._field_generators[field]
        if generator is None:
            return self.IGNORE_FIELD
        value = generator.get_value()
        return value

    def process_field(self, instance, field):
        value = self.get_value(field)
        if value is self.IGNORE_FIELD:
            return
        setattr(instance, field.name, value)

    def process_m2m(self, instance, field):
        # check django's version number to determine how intermediary models
        # are checked if they are auto created or not.
        from django import VERSION
        auto_created_through_model = False
        through = field.rel.through
        if VERSION < (1,2):
            if through:
                if isinstance(through, basestring):
                    bits = through.split('.')
                    if len(bits) < 2:
                        bits = [instance._meta.app_label] + bits
                    through = models.get_model(*bits)
            else:
                auto_created_through_model = True
        else:
            auto_created_through_model = through._meta.auto_created

        if auto_created_through_model:
            return self.process_field(instance, field)
        # if m2m relation has intermediary model:
        #   * only generate relation if 'generate_m2m' is given
        #   * first generate intermediary model and assign a newly created
        #     related model to the foreignkey
        kwargs = {}
        if field.name in self.generate_m2m:
            # get fk to related model on intermediary model
            related_fks = [fk
                for fk in through._meta.fields
                if isinstance(fk, related.ForeignKey) and \
                    fk.rel.to is field.rel.to]
            self_fks = [fk
                for fk in through._meta.fields
                if isinstance(fk, related.ForeignKey) and \
                    fk.rel.to is self.model]
            assert len(related_fks) == 1
            assert len(self_fks) == 1
            related_fk = related_fks[0]
            self_fk = self_fks[0]
            min_count, max_count = self.generate_m2m[field.name]
            intermediary_model = generators.MultipleInstanceGenerator(
                AutoFixture(
                    through,
                    field_values={
                        self_fk.name: instance,
                        related_fk.name: generators.InstanceGenerator(
                            AutoFixture(field.rel.to))
                    }),
                min_count=min_count,
                max_count=max_count,
                **kwargs).generate()

    def check_constrains(self, instance):
        '''
        Return fieldnames which need recalculation.
        '''
        recalc_fields = []
        for constraint in self.constraints:
            try:
                constraint(self.model, instance)
            except constraints.InvalidConstraint, e:
                recalc_fields.extend(e.fields)
        return recalc_fields

    def post_process_instance(self, instance):
        return instance

    def create_one(self, commit=True):
        tries = self.tries
        instance = self.model()
        process = instance._meta.fields
        while process and tries > 0:
            for field in process:
                self.process_field(instance, field)
            process = self.check_constrains(instance)
            tries -= 1
        if tries == 0:
            raise CreateInstanceError(
                u'Cannot solve constraints for "%s", tried %d times. '
                u'Please check value generators or model constraints. '
                u'At least the following fields are involved: %s' % (
                    '%s.%s' % (
                        self.model._meta.app_label,
                        self.model._meta.object_name),
                    self.tries,
                    ', '.join([field.name for field in process]),
            ))
        if commit:
            instance.save()
            for field in instance._meta.many_to_many:
                self.process_m2m(instance, field)
        signals.instance_created.send(
            sender=self,
            model=self.model,
            instance=instance,
            committed=commit)
        return self.post_process_instance(instance)

    def create(self, count=1, commit=True):
        '''
        Create and return ``count`` model instances.

        May raise ``CreateInstanceError`` if constraints are not satisfied.
        '''
        object_list = []
        for i in xrange(count):
            instance = self.create_one(commit=commit)
            object_list.append(instance)
        return object_list

    def iter(self, count=1, commit=True):
        for i in xrange(count):
            yield self.create_one(commit=commit)

    def __iter__(self):
        yield self.create_one()
<|endoftext|>


<commit_before>""" Defines the BarPlot class.
"""
import logging

from numpy import array, compress, column_stack, invert, isnan, transpose, zeros
from enthought.traits.api import Any, Bool, Enum, Float, Instance, Property
from enthought.enable.api import black_color_trait
from enthought.kiva import FILL_STROKE

# Local relative imports
from enthought.chaco.abstract_plot_renderer import AbstractPlotRenderer
from abstract_mapper import AbstractMapper
from array_data_source import ArrayDataSource
from base import reverse_map_1d

logger = logging.getLogger(__name__)

class BarPlot(AbstractPlotRenderer):
    """
    A renderer for bar charts.
    """
    # The data source to use for the index coordinate.
    index = Instance(ArrayDataSource)

    # The data source to use as value points.
    value = Instance(ArrayDataSource)

    # The data source to use as "starting" values for the bars.
    starting_value = Instance(ArrayDataSource)

    # Labels for the indices.
    index_mapper = Instance(AbstractMapper)
    # Labels for the values.
    value_mapper = Instance(AbstractMapper)

    # The orientation of the index axis.
    orientation = Enum("h", "v")

    # The direction of the index axis with respect to the graphics context's
    # direction.
    index_direction = Enum("normal", "flipped")

    # The direction of the value axis with respect to the graphics context's
    # direction.
    value_direction = Enum("normal", "flipped")

    # Type of width used for bars:
    #
    # 'data'
    #     The width is in the units along the x-dimension of the data space.
    # 'screen'
    #     The width uses a fixed width of pixels.
    bar_width_type = Enum("data", "screen")

    # Width of the bars, in data or screen space (determined by
    # **bar_width_type**).
    bar_width = Float(10)

    # Round on rectangle dimensions? This is not strictly an "antialias", but
    # it has the same effect through exact pixel drawing.
    antialias = Bool(True)

    # Width of the border of the bars.
    line_width = Float(1.0)
    # Color of the border of the bars.
    line_color = black_color_trait
    # Color to fill the bars.
    fill_color = black_color_trait

    #use_draw_order = False

    # Convenience properties that correspond to either index_mapper or
    # value_mapper, depending on the orientation of the plot.

    # Corresponds to either **index_mapper** or **value_mapper**, depending on
    # the orientation of the plot.
    x_mapper = Property
    # Corresponds to either **value_mapper** or **index_mapper**, depending on
    # the orientation of the plot.
    y_mapper = Property

    # Corresponds to either **index_direction** or **value_direction**,
    # depending on the orientation of the plot.
    x_direction = Property
    # Corresponds to either **value_direction** or **index_direction**,
    # depending on the orientation of the plot
    y_direction = Property

    # Convenience property for accessing the index data range.
    index_range = Property
    # Convenience property for accessing the value data range.
    value_range = Property

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    # Indicates whether or not the data cache is valid
    _cache_valid = Bool(False)

    # Cached data values from the datasources.  If **bar_width_type** is "data",
    # then this is an Nx4 array of (bar_left, bar_right, start, end) for a
    # bar plot in normal orientation.  If **bar_width_type** is "screen", then
    # this is an Nx3 array of (bar_center, start, end).
    _cached_data_pts = Any

    #------------------------------------------------------------------------
    # AbstractPlotRenderer interface
    #------------------------------------------------------------------------

    def map_screen(self, data_array):
        """ Maps an array of data points into screen space and returns it as
        an array.

        Implements the AbstractPlotRenderer interface.
        """
        # data_array is Nx2 array
        if len(data_array) == 0:
            return []
        x_ary, y_ary = transpose(data_array)
        sx = self.index_mapper.map_screen(x_ary)
        sy = self.value_mapper.map_screen(y_ary)

        # reverse the directions as indicated
        if self.index_direction == "flipped":
            x_sign = -1.0
            x_offset = self.bounds[0]
        else:
            x_sign = 1.0
            x_offset = 0.0

        if self.value_direction == "flipped":
            y_sign = -1.0
            y_offset = self.bounds[1]
        else:
            y_sign = 1.0
            y_offset = 0.0

        # now return map based on orientation
        if self.orientation == "h":
            return transpose(array((x_sign*sx+x_offset,y_sign*sy+y_offset)))
        else:
            return transpose(array((y_sign*sy+y_offset,x_sign*sx+x_offset)))

    def map_data(self, screen_pt):
        """ Maps a screen space point into the "index" space of the plot.

        Implements the AbstractPlotRenderer interface.
        """
        if self.orientation == "h":
            screen_coord = screen_pt[0]
        else:
            screen_coord = screen_pt[1]
        return self.index_mapper.map_data(screen_coord)

    def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, \
                  index_only=False):
        """ Maps a screen space point to an index into the plot's index array(s).

        Implements the AbstractPlotRenderer interface.
        """
        data_pt = self.map_data(screen_pt)
        if ((data_pt < self.index_mapper.range.low) or \
            (data_pt > self.index_mapper.range.high)) and outside_returns_none:
            return None
        half = threshold / 2.0
        index_data = self.index.get_data()
        value_data = self.value.get_data()

        if len(value_data) == 0 or len(index_data) == 0:
            return None

        try:
            ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order)
        except IndexError:
            return None

        x = index_data[ndx]
        y = value_data[ndx]

        result = self.map_screen(array([[x,y]]))
        if result is None:
            return None

        sx, sy = result[0]
        if index_only and ((screen_pt[0]-sx) < threshold):
            return ndx
        elif ((screen_pt[0]-sx)**2 + (screen_pt[1]-sy)**2 < threshold*threshold):
            return ndx
        else:
            return None

    #------------------------------------------------------------------------
    # PlotComponent interface
    #------------------------------------------------------------------------

    def _gather_points(self):
        """ Collects data points that are within the range of the plot, and
        caches them in **_cached_data_pts**.
        """
        index, index_mask = self.index.get_data_mask()
        value, value_mask = self.value.get_data_mask()

        if not self.index or not self.value:
            return

        if len(index) == 0 or len(value) == 0 or len(index) != len(value):
            logger.warn("Chaco: using empty dataset; index_len=%d, value_len=%d." \
                                % (len(index), len(value)))
            self._cached_data_pts = array([])
            self._cache_valid = True
            return

        # TODO: Until we code up a better handling of value-based culling that
        # takes into account starting_value and dataspace bar widths, just use
        # the index culling for now.
#        value_range_mask = self.value_mapper.range.mask_data(value)
#        nan_mask = invert(isnan(index_mask)) & invert(isnan(value_mask))
#        point_mask = index_mask & value_mask & nan_mask & \
#                     index_range_mask & value_range_mask

        index_range_mask = self.index_mapper.range.mask_data(index)
        nan_mask = invert(isnan(index_mask))
        point_mask = index_mask & nan_mask & index_range_mask

        if self.starting_value is None:
            starting_values = zeros(len(index))
        else:
            starting_values = self.starting_value.get_data()

        if self.bar_width_type == "data":
            half_width = self.bar_width / 2.0
            points = column_stack((index-half_width, index+half_width,
                                   starting_values, value))
        else:
            points = column_stack((index, starting_values, value))
        self._cached_data_pts = compress(point_mask, points, axis=0)

        self._cache_valid = True
        return

    def _draw_plot(self, gc, view_bounds=None, mode="normal"):
        """ Draws the 'plot' layer.
        """
        if not self._cache_valid:
            self._gather_points()

        data = self._cached_data_pts
        if data.size == 0:
            # Nothing to draw.
            return

        gc.save_state()
        gc.clip_to_rect(self.x, self.y, self.width, self.height)
        gc.set_antialias(self.antialias)
        gc.set_stroke_color(self.line_color_)
        gc.set_fill_color(self.fill_color_)
        gc.set_line_width(self.line_width)

        if self.bar_width_type == "data":
            # map the bar start and stop locations into screen space
            lower_left_pts = self.map_screen(data[:,(0,2)])
            upper_right_pts = self.map_screen(data[:,(1,3)])
        else:
            half_width = self.bar_width / 2.0
            # map the bar centers into screen space and then compute the bar
            # start and end positions
            lower_left_pts = self.map_screen(data[:,(0,1)])
            upper_right_pts = self.map_screen(data[:,(0,2)])
            lower_left_pts[:,0] -= half_width
            upper_right_pts[:,0] += half_width

        bounds = upper_right_pts - lower_left_pts
        gc.rects(column_stack((lower_left_pts, bounds)))
        gc.draw_path()
        gc.restore_state()

    def _draw_default_axes(self, gc):
        if not self.origin_axis_visible:
            return
        gc.save_state()
        gc.set_stroke_color(self.origin_axis_color_)
        gc.set_line_width(self.origin_axis_width)
        gc.set_line_dash(None)

        for range in (self.index_mapper.range, self.value_mapper.range):
            if (range.low < 0) and (range.high > 0):
                if range == self.index_mapper.range:
                    dual = self.value_mapper.range
                    data_pts = array([[0.0,dual.low], [0.0, dual.high]])
                else:
                    dual = self.index_mapper.range
                    data_pts = array([[dual.low,0.0], [dual.high,0.0]])
                start,end = self.map_screen(data_pts)
                gc.move_to(int(start[0])+0.5, int(start[1])+0.5)
                gc.line_to(int(end[0])+0.5, int(end[1])+0.5)
                gc.stroke_path()
        gc.restore_state()
        return

    def _render_icon(self, gc, x, y, width, height):
        gc.save_state()
        gc.set_fill_color(self.fill_color_)
        gc.set_stroke_color(self.line_color_)
        gc.rect(x+width/4, y+height/4, width/2, height/2)
        gc.draw_path(FILL_STROKE)
        gc.restore_state()

    def _post_load(self):
        super(BarPlot, self)._post_load()
        return

    #------------------------------------------------------------------------
    # Properties
    #------------------------------------------------------------------------

    def _get_index_range(self):
        return self.index_mapper.range

    def _set_index_range(self, val):
        self.index_mapper.range = val

    def _get_value_range(self):
        return self.value_mapper.range

    def _set_value_range(self, val):
        self.value_mapper.range = val

    def _get_x_mapper(self):
        if self.orientation == "h":
            return self.index_mapper
        else:
            return self.value_mapper

    def _get_y_mapper(self):
        if self.orientation == "h":
            return self.value_mapper
        else:
            return self.index_mapper

    def _get_x_direction(self):
        if self.orientation == "h":
            return self.index_direction
        else:
            return self.value_direction

    def _get_y_direction(self):
        if self.orientation == "h":
            return self.value_direction
        else:
            return self.index_direction

    #------------------------------------------------------------------------
    # Event handlers - these are mostly copied from BaseXYPlot
    #------------------------------------------------------------------------

    def _update_mappers(self):
        """ Updates the index and value mappers. Called by trait change handlers
        for various traits.
        """
        x_mapper = self.index_mapper
        y_mapper = self.value_mapper
        x_dir = self.index_direction
        y_dir = self.value_direction

        if self.orientation == "v":
            x_mapper, y_mapper = y_mapper, x_mapper
            x_dir, y_dir = y_dir, x_dir

        x = self.x
        x2 = self.x2
        y = self.y
        y2 = self.y2

        if x_mapper is not None:
            if x_dir =="normal":
                x_mapper.low_pos = x
                x_mapper.high_pos = x2
            else:
                x_mapper.low_pos = x2
                x_mapper.high_pos = x

        if y_mapper is not None:
            if y_dir == "normal":
                y_mapper.low_pos = y
                y_mapper.high_pos = y2
            else:
                y_mapper.low_pos = y2
                y_mapper.high_pos = y

        self.invalidate_draw()
        self._cache_valid = False

    def _bounds_changed(self, old, new):
        super(BarPlot, self)._bounds_changed(old, new)
        self._update_mappers()

    def _bounds_items_changed(self, event):
        super(BarPlot, self)._bounds_items_changed(event)
        self._update_mappers()

    def _orientation_changed(self):
        self._update_mappers()

    def _index_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._either_data_changed, "data_changed", remove=True)
        if new is not None:
            new.on_trait_change(self._either_data_changed, "data_changed")
        self._either_data_changed()

    def _index_direction_changed(self):
        m = self.index_mapper
        m.low_pos, m.high_pos = m.high_pos, m.low_pos
        self.invalidate_draw()

    def _value_direction_changed(self):
        m = self.value_mapper
        m.low_pos, m.high_pos = m.high_pos, m.low_pos
        self.invalidate_draw()

    def _either_data_changed(self):
        self.invalidate_draw()
        self._cache_valid = False
        self.request_redraw()

    def _value_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._either_data_changed, "data_changed", remove=True)
        if new is not None:
            new.on_trait_change(self._either_data_changed, "data_changed")
        self._either_data_changed()

    def _index_mapper_changed(self, old, new):
        return self._either_mapper_changed(old, new)

    def _value_mapper_changed(self, old, new):
        return self._either_mapper_changed(old, new)

    def _either_mapper_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._mapper_updated_handler, "updated", remove=True)
        if new is not None:
            new.on_trait_change(self._mapper_updated_handler, "updated")
        self.invalidate_draw()

    def _mapper_updated_handler(self):
        self._cache_valid = False
        self.invalidate_draw()
        self.request_redraw()

    def _bar_width_changed(self):
        self._cache_valid = False
        self.invalidate_draw()
        self.request_redraw()

    def _bar_width_type_changed(self):
        self._cache_valid = False
        self.invalidate_draw()
        self.request_redraw()

### EOF ####################################################################
<commit_msg>BUG: Eliminated redundant handling of the index_direction and value_direction attributes in the BarPlot map_screen() method, so these attributes now work.<commit_after>""" Defines the BarPlot class.
"""
import logging

from numpy import array, compress, column_stack, invert, isnan, transpose, zeros
from enthought.traits.api import Any, Bool, Enum, Float, Instance, Property
from enthought.enable.api import black_color_trait
from enthought.kiva import FILL_STROKE

# Local relative imports
from enthought.chaco.abstract_plot_renderer import AbstractPlotRenderer
from abstract_mapper import AbstractMapper
from array_data_source import ArrayDataSource
from base import reverse_map_1d

logger = logging.getLogger(__name__)

class BarPlot(AbstractPlotRenderer):
    """
    A renderer for bar charts.
    """
    # The data source to use for the index coordinate.
    index = Instance(ArrayDataSource)

    # The data source to use as value points.
    value = Instance(ArrayDataSource)

    # The data source to use as "starting" values for the bars.
    starting_value = Instance(ArrayDataSource)

    # Labels for the indices.
    index_mapper = Instance(AbstractMapper)
    # Labels for the values.
    value_mapper = Instance(AbstractMapper)

    # The orientation of the index axis.
    orientation = Enum("h", "v")

    # The direction of the index axis with respect to the graphics context's
    # direction.
    index_direction = Enum("normal", "flipped")

    # The direction of the value axis with respect to the graphics context's
    # direction.
    value_direction = Enum("normal", "flipped")

    # Type of width used for bars:
    #
    # 'data'
    #     The width is in the units along the x-dimension of the data space.
    # 'screen'
    #     The width uses a fixed width of pixels.
    bar_width_type = Enum("data", "screen")

    # Width of the bars, in data or screen space (determined by
    # **bar_width_type**).
    bar_width = Float(10)

    # Round on rectangle dimensions? This is not strictly an "antialias", but
    # it has the same effect through exact pixel drawing.
    antialias = Bool(True)

    # Width of the border of the bars.
    line_width = Float(1.0)
    # Color of the border of the bars.
    line_color = black_color_trait
    # Color to fill the bars.
    fill_color = black_color_trait

    #use_draw_order = False

    # Convenience properties that correspond to either index_mapper or
    # value_mapper, depending on the orientation of the plot.

    # Corresponds to either **index_mapper** or **value_mapper**, depending on
    # the orientation of the plot.
    x_mapper = Property
    # Corresponds to either **value_mapper** or **index_mapper**, depending on
    # the orientation of the plot.
    y_mapper = Property

    # Corresponds to either **index_direction** or **value_direction**,
    # depending on the orientation of the plot.
    x_direction = Property
    # Corresponds to either **value_direction** or **index_direction**,
    # depending on the orientation of the plot
    y_direction = Property

    # Convenience property for accessing the index data range.
    index_range = Property
    # Convenience property for accessing the value data range.
    value_range = Property

    #------------------------------------------------------------------------
    # Private traits
    #------------------------------------------------------------------------

    # Indicates whether or not the data cache is valid
    _cache_valid = Bool(False)

    # Cached data values from the datasources.  If **bar_width_type** is "data",
    # then this is an Nx4 array of (bar_left, bar_right, start, end) for a
    # bar plot in normal orientation.  If **bar_width_type** is "screen", then
    # this is an Nx3 array of (bar_center, start, end).
    _cached_data_pts = Any

    #------------------------------------------------------------------------
    # AbstractPlotRenderer interface
    #------------------------------------------------------------------------

    def map_screen(self, data_array):
        """ Maps an array of data points into screen space and returns it as
        an array.

        Implements the AbstractPlotRenderer interface.
        """
        # data_array is Nx2 array
        if len(data_array) == 0:
            return []
        x_ary, y_ary = transpose(data_array)
        sx = self.index_mapper.map_screen(x_ary)
        sy = self.value_mapper.map_screen(y_ary)

        if self.orientation == "h":
            return transpose(array((sx,sy)))
        else:
            return transpose(array((sy,sx)))

    def map_data(self, screen_pt):
        """ Maps a screen space point into the "index" space of the plot.

        Implements the AbstractPlotRenderer interface.
        """
        if self.orientation == "h":
            screen_coord = screen_pt[0]
        else:
            screen_coord = screen_pt[1]
        return self.index_mapper.map_data(screen_coord)

    def map_index(self, screen_pt, threshold=2.0, outside_returns_none=True, \
                  index_only=False):
        """ Maps a screen space point to an index into the plot's index array(s).

        Implements the AbstractPlotRenderer interface.
        """
        data_pt = self.map_data(screen_pt)
        if ((data_pt < self.index_mapper.range.low) or \
            (data_pt > self.index_mapper.range.high)) and outside_returns_none:
            return None
        half = threshold / 2.0
        index_data = self.index.get_data()
        value_data = self.value.get_data()

        if len(value_data) == 0 or len(index_data) == 0:
            return None

        try:
            ndx = reverse_map_1d(index_data, data_pt, self.index.sort_order)
        except IndexError:
            return None

        x = index_data[ndx]
        y = value_data[ndx]

        result = self.map_screen(array([[x,y]]))
        if result is None:
            return None

        sx, sy = result[0]
        if index_only and ((screen_pt[0]-sx) < threshold):
            return ndx
        elif ((screen_pt[0]-sx)**2 + (screen_pt[1]-sy)**2 < threshold*threshold):
            return ndx
        else:
            return None

    #------------------------------------------------------------------------
    # PlotComponent interface
    #------------------------------------------------------------------------

    def _gather_points(self):
        """ Collects data points that are within the range of the plot, and
        caches them in **_cached_data_pts**.
        """
        index, index_mask = self.index.get_data_mask()
        value, value_mask = self.value.get_data_mask()

        if not self.index or not self.value:
            return

        if len(index) == 0 or len(value) == 0 or len(index) != len(value):
            logger.warn("Chaco: using empty dataset; index_len=%d, value_len=%d." \
                                % (len(index), len(value)))
            self._cached_data_pts = array([])
            self._cache_valid = True
            return

        # TODO: Until we code up a better handling of value-based culling that
        # takes into account starting_value and dataspace bar widths, just use
        # the index culling for now.
#        value_range_mask = self.value_mapper.range.mask_data(value)
#        nan_mask = invert(isnan(index_mask)) & invert(isnan(value_mask))
#        point_mask = index_mask & value_mask & nan_mask & \
#                     index_range_mask & value_range_mask

        index_range_mask = self.index_mapper.range.mask_data(index)
        nan_mask = invert(isnan(index_mask))
        point_mask = index_mask & nan_mask & index_range_mask

        if self.starting_value is None:
            starting_values = zeros(len(index))
        else:
            starting_values = self.starting_value.get_data()

        if self.bar_width_type == "data":
            half_width = self.bar_width / 2.0
            points = column_stack((index-half_width, index+half_width,
                                   starting_values, value))
        else:
            points = column_stack((index, starting_values, value))
        self._cached_data_pts = compress(point_mask, points, axis=0)

        self._cache_valid = True
        return

    def _draw_plot(self, gc, view_bounds=None, mode="normal"):
        """ Draws the 'plot' layer.
        """
        if not self._cache_valid:
            self._gather_points()

        data = self._cached_data_pts
        if data.size == 0:
            # Nothing to draw.
            return

        gc.save_state()
        gc.clip_to_rect(self.x, self.y, self.width, self.height)
        gc.set_antialias(self.antialias)
        gc.set_stroke_color(self.line_color_)
        gc.set_fill_color(self.fill_color_)
        gc.set_line_width(self.line_width)

        if self.bar_width_type == "data":
            # map the bar start and stop locations into screen space
            lower_left_pts = self.map_screen(data[:,(0,2)])
            upper_right_pts = self.map_screen(data[:,(1,3)])
        else:
            half_width = self.bar_width / 2.0
            # map the bar centers into screen space and then compute the bar
            # start and end positions
            lower_left_pts = self.map_screen(data[:,(0,1)])
            upper_right_pts = self.map_screen(data[:,(0,2)])
            lower_left_pts[:,0] -= half_width
            upper_right_pts[:,0] += half_width

        bounds = upper_right_pts - lower_left_pts
        gc.rects(column_stack((lower_left_pts, bounds)))
        gc.draw_path()
        gc.restore_state()

    def _draw_default_axes(self, gc):
        if not self.origin_axis_visible:
            return
        gc.save_state()
        gc.set_stroke_color(self.origin_axis_color_)
        gc.set_line_width(self.origin_axis_width)
        gc.set_line_dash(None)

        for range in (self.index_mapper.range, self.value_mapper.range):
            if (range.low < 0) and (range.high > 0):
                if range == self.index_mapper.range:
                    dual = self.value_mapper.range
                    data_pts = array([[0.0,dual.low], [0.0, dual.high]])
                else:
                    dual = self.index_mapper.range
                    data_pts = array([[dual.low,0.0], [dual.high,0.0]])
                start,end = self.map_screen(data_pts)
                gc.move_to(int(start[0])+0.5, int(start[1])+0.5)
                gc.line_to(int(end[0])+0.5, int(end[1])+0.5)
                gc.stroke_path()
        gc.restore_state()
        return

    def _render_icon(self, gc, x, y, width, height):
        gc.save_state()
        gc.set_fill_color(self.fill_color_)
        gc.set_stroke_color(self.line_color_)
        gc.rect(x+width/4, y+height/4, width/2, height/2)
        gc.draw_path(FILL_STROKE)
        gc.restore_state()

    def _post_load(self):
        super(BarPlot, self)._post_load()
        return

    #------------------------------------------------------------------------
    # Properties
    #------------------------------------------------------------------------

    def _get_index_range(self):
        return self.index_mapper.range

    def _set_index_range(self, val):
        self.index_mapper.range = val

    def _get_value_range(self):
        return self.value_mapper.range

    def _set_value_range(self, val):
        self.value_mapper.range = val

    def _get_x_mapper(self):
        if self.orientation == "h":
            return self.index_mapper
        else:
            return self.value_mapper

    def _get_y_mapper(self):
        if self.orientation == "h":
            return self.value_mapper
        else:
            return self.index_mapper

    def _get_x_direction(self):
        if self.orientation == "h":
            return self.index_direction
        else:
            return self.value_direction

    def _get_y_direction(self):
        if self.orientation == "h":
            return self.value_direction
        else:
            return self.index_direction

    #------------------------------------------------------------------------
    # Event handlers - these are mostly copied from BaseXYPlot
    #------------------------------------------------------------------------

    def _update_mappers(self):
        """ Updates the index and value mappers. Called by trait change handlers
        for various traits.
        """
        x_mapper = self.index_mapper
        y_mapper = self.value_mapper
        x_dir = self.index_direction
        y_dir = self.value_direction

        if self.orientation == "v":
            x_mapper, y_mapper = y_mapper, x_mapper
            x_dir, y_dir = y_dir, x_dir

        x = self.x
        x2 = self.x2
        y = self.y
        y2 = self.y2

        if x_mapper is not None:
            if x_dir =="normal":
                x_mapper.low_pos = x
                x_mapper.high_pos = x2
            else:
                x_mapper.low_pos = x2
                x_mapper.high_pos = x

        if y_mapper is not None:
            if y_dir == "normal":
                y_mapper.low_pos = y
                y_mapper.high_pos = y2
            else:
                y_mapper.low_pos = y2
                y_mapper.high_pos = y

        self.invalidate_draw()
        self._cache_valid = False

    def _bounds_changed(self, old, new):
        super(BarPlot, self)._bounds_changed(old, new)
        self._update_mappers()

    def _bounds_items_changed(self, event):
        super(BarPlot, self)._bounds_items_changed(event)
        self._update_mappers()

    def _orientation_changed(self):
        self._update_mappers()

    def _index_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._either_data_changed, "data_changed", remove=True)
        if new is not None:
            new.on_trait_change(self._either_data_changed, "data_changed")
        self._either_data_changed()

    def _index_direction_changed(self):
        m = self.index_mapper
        m.low_pos, m.high_pos = m.high_pos, m.low_pos
        self.invalidate_draw()

    def _value_direction_changed(self):
        m = self.value_mapper
        m.low_pos, m.high_pos = m.high_pos, m.low_pos
        self.invalidate_draw()

    def _either_data_changed(self):
        self.invalidate_draw()
        self._cache_valid = False
        self.request_redraw()

    def _value_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._either_data_changed, "data_changed", remove=True)
        if new is not None:
            new.on_trait_change(self._either_data_changed, "data_changed")
        self._either_data_changed()

    def _index_mapper_changed(self, old, new):
        return self._either_mapper_changed(old, new)

    def _value_mapper_changed(self, old, new):
        return self._either_mapper_changed(old, new)

    def _either_mapper_changed(self, old, new):
        if old is not None:
            old.on_trait_change(self._mapper_updated_handler, "updated", remove=True)
        if new is not None:
            new.on_trait_change(self._mapper_updated_handler, "updated")
        self.invalidate_draw()

    def _mapper_updated_handler(self):
        self._cache_valid = False
        self.invalidate_draw()
        self.request_redraw()

    def _bar_width_changed(self):
        self._cache_valid = False
        self.invalidate_draw()
        self.request_redraw()

    def _bar_width_type_changed(self):
        self._cache_valid = False
        self.invalidate_draw()
        self.request_redraw()

### EOF ####################################################################
<|endoftext|>