<commit_before>import json
import time

from nose.tools import eq_
from rest_framework.test import APIClient

from django.conf import settings

from kitsune.search.tests.test_es import ElasticTestCase
from kitsune.sumo.urlresolvers import reverse
from kitsune.questions.tests import question, answer
from kitsune.products.tests import product
from kitsune.wiki.tests import document, revision

class SuggestViewTests(ElasticTestCase):
    client_class = APIClient

    def _make_question(self, solved=True, **kwargs):
        defaults = {
            'title': 'Login to website comments disabled ' + str(time.time()),
            'content': """
                readersupportednews.org, sends me emails with a list of
                articles to read.

                The links to the articles work as normal, except that I
                cannot login from the linked article - as required - to
                send my comments.

                I see a javascript activity statement at the bottom left
                corner of my screen while the left button is depressed
                on the Login button. it is gone when I release the left
                button, but no results.

                I have the latest (7) version of java enabled, on an XP
                box.

                Why this inability to login to this website commentary?
                """,
            'save': True,
        }
        defaults.update(kwargs)
        q = question(**defaults)
        if solved:
            a = answer(question=q, save=True)
            q.solution = a
        # Trigger a reindex for the question.
        q.save()
        return q

    def _make_document(self, **kwargs):
        defaults = {
            'title': 'How to make a pie from scratch with email ' + str(time.time()),
            'category': 10,
            'save': True
        }

        defaults.update(kwargs)
        d = document(**defaults)
        revision(document=d, is_approved=True, save=True)
        d.save()
        return d

    def test_invalid_product(self):
        res = self.client.get(reverse('search.suggest'), {
            'product': 'nonexistant',
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {'product': [u'Could not find product with slug "nonexistant".']})

    def test_invalid_locale(self):
        res = self.client.get(reverse('search.suggest'), {
            'locale': 'bad-medicine',
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {'locale': [u'Could not find locale "bad-medicine".']})

    def test_invalid_fallback_locale_none_case(self):
        # Test the locale -> locale case.
        non_none_locale_fallback_pairs = [
            (key, val) for key, val in sorted(settings.NON_SUPPORTED_LOCALES.items())
            if val is not None
        ]
        locale, fallback = non_none_locale_fallback_pairs[0]

        res = self.client.get(reverse('search.suggest'), {
            'locale': locale,
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(
            res.data['detail'],
            {'locale': [u'"{0}" is not supported, but has fallback locale "{1}".'.format(
                locale, fallback)]}
        )

    def test_invalid_fallback_locale_non_none_case(self):
        # Test the locale -> None case which falls back to WIKI_DEFAULT_LANGUAGE.
        has_none_locale_fallback_pairs = [
            (key, val) for key, val in sorted(settings.NON_SUPPORTED_LOCALES.items())
            if val is None
        ]
        locale, fallback = has_none_locale_fallback_pairs[0]

        res = self.client.get(reverse('search.suggest'), {
            'locale': locale,
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(
            res.data['detail'],
            {'locale': [u'"{0}" is not supported, but has fallback locale "{1}".'.format(
                locale, settings.WIKI_DEFAULT_LANGUAGE)]}
        )

    def test_invalid_numbers(self):
        res = self.client.get(reverse('search.suggest'), {
            'max_questions': 'a',
            'max_documents': 'b',
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {
            'max_questions': [u'Enter a whole number.'],
            'max_documents': [u'Enter a whole number.'],
        })

    def test_q_required(self):
        res = self.client.get(reverse('search.suggest'))
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {'q': [u'This field is required.']})

    def test_it_works(self):
        q1 = self._make_question()
        d1 = self._make_document()
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_([q['id'] for q in req.data['questions']], [q1.id])
        eq_([d['title'] for d in req.data['documents']], [d1.title])

    def test_filters_in_postdata(self):
        q1 = self._make_question()
        d1 = self._make_document()
        self.refresh()

        data = json.dumps({'q': 'emails'})
        # Note: Have to use .generic() because .get() will convert the
        # data into querystring params and then it's clownshoes all
        # the way down.
        req = self.client.generic(
            'GET', reverse('search.suggest'), data=data,
            content_type='application/json')
        eq_(req.status_code, 200)
        eq_([q['id'] for q in req.data['questions']], [q1.id])
        eq_([d['title'] for d in req.data['documents']], [d1.title])

    def test_both_querystring_and_body_raises_error(self):
        self._make_question()
        self._make_document()
        self.refresh()

        data = json.dumps({'q': 'emails'})
        # Note: Have to use .generic() because .get() will convert the
        # data into querystring params and then it's clownshoes all
        # the way down.
        req = self.client.generic(
            'GET', reverse('search.suggest') + '?max_documents=3', data=data,
            content_type='application/json')
        eq_(req.status_code, 400)
        eq_(req.data,
            {u'detail': 'Put all parameters either in the querystring or the HTTP request body.'})

    def test_questions_max_results_0(self):
        self._make_question()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['questions']), 1)

        # If we specify "don't give me any" make sure we don't get any.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_questions': '0'})
        eq_(len(req.data['questions']), 0)

    def test_questions_max_results_non_0(self):
        self._make_question()
        self._make_question()
        self._make_question()
        self._make_question()
        self._make_question()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['questions']), 5)

        # If we specify "don't give me any" make sure we don't get any.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_questions': '3'})
        eq_(len(req.data['questions']), 3)

    def test_documents_max_results_0(self):
        self._make_document()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['documents']), 1)

        # If we specify "don't give me any" make sure we don't get any.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_documents': '0'})
        eq_(len(req.data['documents']), 0)

    def test_documents_max_results_non_0(self):
        self._make_document()
        self._make_document()
        self._make_document()
        self._make_document()
        self._make_document()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['documents']), 5)

        # If we specify "don't give me any" make sure we don't get any.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_documents': '3'})
        eq_(len(req.data['documents']), 3)

    def test_product_filter_works(self):
        p1 = product(save=True)
        p2 = product(save=True)
        q1 = self._make_question(product=p1)
        self._make_question(product=p2)
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'product': p1.slug})
        eq_([q['id'] for q in req.data['questions']], [q1.id])

    def test_locale_filter_works_for_questions(self):
        q1 = self._make_question(locale='fr')
        self._make_question(locale='en-US')
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'locale': 'fr'})
        eq_([q['id'] for q in req.data['questions']], [q1.id])

    def test_locale_filter_works_for_documents(self):
        d1 = self._make_document(slug='right-doc', locale='fr')
        self._make_document(slug='wrong-doc', locale='en-US')
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'locale': 'fr'})
        eq_([d['slug'] for d in req.data['documents']], [d1.slug])

    def test_serializer_fields(self):
        """Test that fields from the serializer are included."""
        self._make_question()
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        # Check that a field that is only available from the DB is in the response.
        assert 'metadata' in req.data['questions'][0]

    def test_only_solved(self):
        """Test that only solved questions are suggested."""
        q1 = self._make_question(solved=True)
        q2 = self._make_question(solved=False)
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        ids = [q['id'] for q in req.data['questions']]
        assert q1.id in ids
        assert q2.id not in ids
        eq_(len(ids), 1)
<commit_msg>[bug 1164565] Fix comments in tests<commit_after>import json
import time

from nose.tools import eq_
from rest_framework.test import APIClient

from django.conf import settings

from kitsune.search.tests.test_es import ElasticTestCase
from kitsune.sumo.urlresolvers import reverse
from kitsune.questions.tests import question, answer
from kitsune.products.tests import product
from kitsune.wiki.tests import document, revision

class SuggestViewTests(ElasticTestCase):
    client_class = APIClient

    def _make_question(self, solved=True, **kwargs):
        defaults = {
            'title': 'Login to website comments disabled ' + str(time.time()),
            'content': """
                readersupportednews.org, sends me emails with a list of
                articles to read.

                The links to the articles work as normal, except that I
                cannot login from the linked article - as required - to
                send my comments.

                I see a javascript activity statement at the bottom left
                corner of my screen while the left button is depressed
                on the Login button. it is gone when I release the left
                button, but no results.

                I have the latest (7) version of java enabled, on an XP
                box.

                Why this inability to login to this website commentary?
                """,
            'save': True,
        }
        defaults.update(kwargs)
        q = question(**defaults)
        if solved:
            a = answer(question=q, save=True)
            q.solution = a
        # Trigger a reindex for the question.
        q.save()
        return q

    def _make_document(self, **kwargs):
        defaults = {
            'title': 'How to make a pie from scratch with email ' + str(time.time()),
            'category': 10,
            'save': True
        }

        defaults.update(kwargs)
        d = document(**defaults)
        revision(document=d, is_approved=True, save=True)
        d.save()
        return d

    def test_invalid_product(self):
        res = self.client.get(reverse('search.suggest'), {
            'product': 'nonexistant',
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {'product': [u'Could not find product with slug "nonexistant".']})

    def test_invalid_locale(self):
        res = self.client.get(reverse('search.suggest'), {
            'locale': 'bad-medicine',
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {'locale': [u'Could not find locale "bad-medicine".']})

    def test_invalid_fallback_locale_none_case(self):
        # Test the locale -> locale case.
        non_none_locale_fallback_pairs = [
            (key, val) for key, val in sorted(settings.NON_SUPPORTED_LOCALES.items())
            if val is not None
        ]
        locale, fallback = non_none_locale_fallback_pairs[0]

        res = self.client.get(reverse('search.suggest'), {
            'locale': locale,
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(
            res.data['detail'],
            {'locale': [u'"{0}" is not supported, but has fallback locale "{1}".'.format(
                locale, fallback)]}
        )

    def test_invalid_fallback_locale_non_none_case(self):
        # Test the locale -> None case which falls back to WIKI_DEFAULT_LANGUAGE.
        has_none_locale_fallback_pairs = [
            (key, val) for key, val in sorted(settings.NON_SUPPORTED_LOCALES.items())
            if val is None
        ]
        locale, fallback = has_none_locale_fallback_pairs[0]

        res = self.client.get(reverse('search.suggest'), {
            'locale': locale,
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(
            res.data['detail'],
            {'locale': [u'"{0}" is not supported, but has fallback locale "{1}".'.format(
                locale, settings.WIKI_DEFAULT_LANGUAGE)]}
        )

    def test_invalid_numbers(self):
        res = self.client.get(reverse('search.suggest'), {
            'max_questions': 'a',
            'max_documents': 'b',
            'q': 'search',
        })
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {
            'max_questions': [u'Enter a whole number.'],
            'max_documents': [u'Enter a whole number.'],
        })

    def test_q_required(self):
        res = self.client.get(reverse('search.suggest'))
        eq_(res.status_code, 400)
        eq_(res.data['detail'], {'q': [u'This field is required.']})

    def test_it_works(self):
        q1 = self._make_question()
        d1 = self._make_document()
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_([q['id'] for q in req.data['questions']], [q1.id])
        eq_([d['title'] for d in req.data['documents']], [d1.title])

    def test_filters_in_postdata(self):
        q1 = self._make_question()
        d1 = self._make_document()
        self.refresh()

        data = json.dumps({'q': 'emails'})
        # Note: Have to use .generic() because .get() will convert the
        # data into querystring params and then it's clownshoes all
        # the way down.
        req = self.client.generic(
            'GET', reverse('search.suggest'), data=data,
            content_type='application/json')
        eq_(req.status_code, 200)
        eq_([q['id'] for q in req.data['questions']], [q1.id])
        eq_([d['title'] for d in req.data['documents']], [d1.title])

    def test_both_querystring_and_body_raises_error(self):
        self._make_question()
        self._make_document()
        self.refresh()

        data = json.dumps({'q': 'emails'})
        # Note: Have to use .generic() because .get() will convert the
        # data into querystring params and then it's clownshoes all
        # the way down.
        req = self.client.generic(
            'GET', reverse('search.suggest') + '?max_documents=3', data=data,
            content_type='application/json')
        eq_(req.status_code, 400)
        eq_(req.data,
            {u'detail': 'Put all parameters either in the querystring or the HTTP request body.'})

    def test_questions_max_results_0(self):
        self._make_question()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['questions']), 1)

        # If we specify "don't give me any" make sure we don't get any.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_questions': '0'})
        eq_(len(req.data['questions']), 0)

    def test_questions_max_results_non_0(self):
        self._make_question()
        self._make_question()
        self._make_question()
        self._make_question()
        self._make_question()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['questions']), 5)

        # Make sure we get only 3.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_questions': '3'})
        eq_(len(req.data['questions']), 3)

    def test_documents_max_results_0(self):
        self._make_document()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['documents']), 1)

        # If we specify "don't give me any" make sure we don't get any.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_documents': '0'})
        eq_(len(req.data['documents']), 0)

    def test_documents_max_results_non_0(self):
        self._make_document()
        self._make_document()
        self._make_document()
        self._make_document()
        self._make_document()
        self.refresh()

        # Make sure something matches the query first.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        eq_(len(req.data['documents']), 5)

        # Make sure we get only 3.
        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'max_documents': '3'})
        eq_(len(req.data['documents']), 3)

    def test_product_filter_works(self):
        p1 = product(save=True)
        p2 = product(save=True)
        q1 = self._make_question(product=p1)
        self._make_question(product=p2)
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'product': p1.slug})
        eq_([q['id'] for q in req.data['questions']], [q1.id])

    def test_locale_filter_works_for_questions(self):
        q1 = self._make_question(locale='fr')
        self._make_question(locale='en-US')
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'locale': 'fr'})
        eq_([q['id'] for q in req.data['questions']], [q1.id])

    def test_locale_filter_works_for_documents(self):
        d1 = self._make_document(slug='right-doc', locale='fr')
        self._make_document(slug='wrong-doc', locale='en-US')
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails', 'locale': 'fr'})
        eq_([d['slug'] for d in req.data['documents']], [d1.slug])

    def test_serializer_fields(self):
        """Test that fields from the serializer are included."""
        self._make_question()
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        # Check that a field that is only available from the DB is in the response.
        assert 'metadata' in req.data['questions'][0]

    def test_only_solved(self):
        """Test that only solved questions are suggested."""
        q1 = self._make_question(solved=True)
        q2 = self._make_question(solved=False)
        self.refresh()

        req = self.client.get(reverse('search.suggest'), {'q': 'emails'})
        ids = [q['id'] for q in req.data['questions']]
        assert q1.id in ids
        assert q2.id not in ids
        eq_(len(ids), 1)
<|endoftext|>


<commit_before># coding: utf-8
from __future__ import unicode_literals

from django.conf import settings
from formpack import FormPack

from .constants import (SPECIFIC_REPORTS_KEY, DEFAULT_REPORTS_KEY
                        )

from copy import deepcopy

def get_instances_for_userform_id(userform_id, submission=None):
    query = {'_userform_id': userform_id, '_deleted_at': {'$exists': False}}
    if submission:
        query['_id'] = submission
    return settings.MONGO_DB.instances.find(query)

def _kuids(asset, cache=False):
    if not cache or not hasattr(asset, '_available_report_uids'):
        content = deepcopy(asset.content)
        survey = content.get('survey', [])
        asset._available_report_uids = [
            row.get('$kuid') for row in survey
        ]
    return asset._available_report_uids

def get_kuid_to_variable_name_map(asset):
    '''
    Retrieve a mapping from "kuids", which :py:mod:`formpack` is not aware of,
    to the corresponding XLSForm variable name.

    :param Asset asset: The asset to make mappings for.
    :rtype: dict[basesting, basestring]
    '''

    survey_dict = asset.content.get('survey', [])
    kuid_to_variable_name_map = {row.get('$kuid'): row.get('name') for row in survey_dict}
    return kuid_to_variable_name_map

def data(asset, kuids, lang=None, fields=None, split_by=None):
    schema = {
        "id_string": asset.deployment.xform_id_string,
        "version": 'v1',
        "content": asset.valid_xlsform_content(),
    }

    pack = FormPack([schema])
    report = pack.autoreport()
    fields = fields or [field.name for field in pack.get_fields_for_versions()]
    translations = pack.available_translations
    lang = lang or next(iter(translations), None)

    _data = [("v1", get_instances_for_userform_id(asset.deployment.mongo_userform_id))]
    stats = list(report.get_stats(_data, fields, lang, split_by).stats)
    report_data_by_variable_name = dict()
    for s in stats:
        form_field = s[0]
        label = s[1]
        stats_dict = s[2]

        variable_name = form_field.name
        field_type = form_field.data_type

        # Modify `stats_dict` so it's more amenable to quick use with Chart.js.
        freq = stats_dict.pop('frequency', None)
        if freq:
            prcntg = stats_dict.pop('percentage')
            responses, frequencies = zip(*freq)
            responses_percentage, percentages = zip(*prcntg)
            if responses != responses_percentage:
                raise ValueError('Frequency and percentage response lists mismatch.')

            stats_dict.update({'responses': responses, 'frequencies': frequencies,
                               'percentages': percentages})

        stats_dict.update({'variable_name': variable_name, 'label': label,
                          'field_type': field_type})
        report_data_by_variable_name[variable_name] = stats_dict

    available_kuids = set(_kuids(asset))
    if kuids:
        available_kuids &= set(kuids)
    kuid_to_variable_name_map = get_kuid_to_variable_name_map(asset)

    data_by_kuid = dict()
    for kuid in available_kuids:
        data_by_kuid[kuid] = report_data_by_variable_name[kuid_to_variable_name_map[kuid]]

    if not asset.report_styles:
        asset._populate_report_styles()
    default_style = asset.report_styles[DEFAULT_REPORTS_KEY]
    specified = asset.report_styles[SPECIFIC_REPORTS_KEY]
    report_styles = {kuid: specified[kuid] for kuid in available_kuids if specified.get(kuid)}

    return [
        {
            'data': data_by_kuid[kuid],
            'style': report_styles.get(kuid, default_style),
            'row': asset._get_row_by_kuid(kuid),
            'kuid': kuid,
        } for kuid in available_kuids
    ]
<commit_msg>no longer wrapping mongo queries in a tuple<commit_after># coding: utf-8
from __future__ import unicode_literals

from django.conf import settings
from formpack import FormPack

from .constants import (SPECIFIC_REPORTS_KEY, DEFAULT_REPORTS_KEY
                        )

from copy import deepcopy

def get_instances_for_userform_id(userform_id, submission=None):
    query = {'_userform_id': userform_id, '_deleted_at': {'$exists': False}}
    if submission:
        query['_id'] = submission
    return settings.MONGO_DB.instances.find(query)

def _kuids(asset, cache=False):
    if not cache or not hasattr(asset, '_available_report_uids'):
        content = deepcopy(asset.content)
        survey = content.get('survey', [])
        asset._available_report_uids = [
            row.get('$kuid') for row in survey
        ]
    return asset._available_report_uids

def get_kuid_to_variable_name_map(asset):
    '''
    Retrieve a mapping from "kuids", which :py:mod:`formpack` is not aware of,
    to the corresponding XLSForm variable name.

    :param Asset asset: The asset to make mappings for.
    :rtype: dict[basesting, basestring]
    '''

    survey_dict = asset.content.get('survey', [])
    kuid_to_variable_name_map = {row.get('$kuid'): row.get('name') for row in survey_dict}
    return kuid_to_variable_name_map

def data(asset, kuids, lang=None, fields=None, split_by=None):
    schema = {
        "id_string": asset.deployment.xform_id_string,
        "version": 'v1',
        "content": asset.valid_xlsform_content(),
    }

    pack = FormPack([schema])
    report = pack.autoreport()
    fields = fields or [field.name for field in pack.get_fields_for_versions()]
    translations = pack.available_translations
    lang = lang or next(iter(translations), None)

    _data = get_instances_for_userform_id(asset.deployment.mongo_userform_id)
    stats = list(report.get_stats(_data, fields, lang, split_by).stats)
    report_data_by_variable_name = dict()
    for s in stats:
        form_field = s[0]
        label = s[1]
        stats_dict = s[2]

        variable_name = form_field.name
        field_type = form_field.data_type

        # Modify `stats_dict` so it's more amenable to quick use with Chart.js.
        freq = stats_dict.pop('frequency', None)
        if freq:
            prcntg = stats_dict.pop('percentage')
            responses, frequencies = zip(*freq)
            responses_percentage, percentages = zip(*prcntg)
            if responses != responses_percentage:
                raise ValueError('Frequency and percentage response lists mismatch.')

            stats_dict.update({'responses': responses, 'frequencies': frequencies,
                               'percentages': percentages})

        stats_dict.update({'variable_name': variable_name, 'label': label,
                          'field_type': field_type})
        report_data_by_variable_name[variable_name] = stats_dict

    available_kuids = set(_kuids(asset))
    if kuids:
        available_kuids &= set(kuids)
    kuid_to_variable_name_map = get_kuid_to_variable_name_map(asset)

    data_by_kuid = dict()
    for kuid in available_kuids:
        data_by_kuid[kuid] = report_data_by_variable_name[kuid_to_variable_name_map[kuid]]

    if not asset.report_styles:
        asset._populate_report_styles()
    default_style = asset.report_styles[DEFAULT_REPORTS_KEY]
    specified = asset.report_styles[SPECIFIC_REPORTS_KEY]
    report_styles = {kuid: specified[kuid] for kuid in available_kuids if specified.get(kuid)}

    return [
        {
            'data': data_by_kuid[kuid],
            'style': report_styles.get(kuid, default_style),
            'row': asset._get_row_by_kuid(kuid),
            'kuid': kuid,
        } for kuid in available_kuids
    ]
<|endoftext|>


<commit_before># Copyright 2020 KMEE INFORMATICA LTDA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
    'name': 'Brazilian Localization Contract',
    'summary': """
        Customization of Contract module for implementations in Brazil.""",
    'version': '12.0.1.0.0',
    'license': 'AGPL-3',
    'author': 'KMEE,Odoo Community Association (OCA)',
    'website': 'https://github.com/oca/l10n-brazil',
    'depends': [
        'l10n_br_account', 'contract'
    ],
    'data': [
        'data/company.xml',

        'views/res_company.xml',
        'views/contract_view.xml',
        'views/contract_line.xml',
    ],
    'demo': [
        'demo/company.xml',
        'demo/contract_demo.xml',
    ],
}
<commit_msg>l10n_br_contract 12.0.2.0.0<commit_after># Copyright 2020 KMEE INFORMATICA LTDA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

{
    'name': 'Brazilian Localization Contract',
    'summary': """
        Customization of Contract module for implementations in Brazil.""",
    'version': '12.0.2.0.0',
    'license': 'AGPL-3',
    'author': 'KMEE,Odoo Community Association (OCA)',
    'website': 'https://github.com/oca/l10n-brazil',
    'depends': [
        'l10n_br_account', 'contract'
    ],
    'data': [
        'data/company.xml',

        'views/res_company.xml',
        'views/contract_view.xml',
        'views/contract_line.xml',
    ],
    'demo': [
        'demo/company.xml',
        'demo/contract_demo.xml',
    ],
}
<|endoftext|>


<commit_before># Copyright 2009 Alejandro Sanchez <alejandro@asr-oss.com>
# Copyright 2015 Ismael Calvo <ismael.calvo@factorlibre.com>
# Copyright 2015 Tecon
# Copyright 2015 Omar Castiñeira (Comunitea)
# Copyright 2016-2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
    "name": "Creación de Factura-e",
    "version": "15.0.1.0.1",
    "author": "ASR-OSS, "
    "FactorLibre, "
    "Tecon, "
    "Comunitea, "
    "Tecnativa, "
    "Creu Blanca, "
    "Odoo Community Association (OCA)",
    "category": "Accounting & Finance",
    "website": "https://github.com/OCA/l10n-spain",
    "license": "AGPL-3",
    "depends": [
        "account_payment_partner",
        "l10n_es_partner",
        "l10n_es",
        "base_iso3166",
        "base_vat",
        "report_xml",
        "report_qweb_parameter",
        "l10n_es_aeat",
    ],
    "data": [
        "security/ir.model.access.csv",
        "data/account_tax_template.xml",
        "views/res_partner_view.xml",
        "views/res_company.xml",
        "views/payment_mode_view.xml",
        "views/account_tax_view.xml",
        "views/report_facturae.xml",
        "wizard/create_facturae_view.xml",
        "wizard/account_move_reversal_view.xml",
        "views/account_move_view.xml",
    ],
    "external_dependencies": {
        "python": ["cryptography", "pyOpenSSL", "pycountry", "xmlsig"]
    },
    "installable": True,
    "maintainers": ["etobella"],
}
<commit_msg>l10n_es_facturae 15.0.1.0.2<commit_after># Copyright 2009 Alejandro Sanchez <alejandro@asr-oss.com>
# Copyright 2015 Ismael Calvo <ismael.calvo@factorlibre.com>
# Copyright 2015 Tecon
# Copyright 2015 Omar Castiñeira (Comunitea)
# Copyright 2016-2020 Tecnativa - Pedro M. Baeza
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
    "name": "Creación de Factura-e",
    "version": "15.0.1.0.2",
    "author": "ASR-OSS, "
    "FactorLibre, "
    "Tecon, "
    "Comunitea, "
    "Tecnativa, "
    "Creu Blanca, "
    "Odoo Community Association (OCA)",
    "category": "Accounting & Finance",
    "website": "https://github.com/OCA/l10n-spain",
    "license": "AGPL-3",
    "depends": [
        "account_payment_partner",
        "l10n_es_partner",
        "l10n_es",
        "base_iso3166",
        "base_vat",
        "report_xml",
        "report_qweb_parameter",
        "l10n_es_aeat",
    ],
    "data": [
        "security/ir.model.access.csv",
        "data/account_tax_template.xml",
        "views/res_partner_view.xml",
        "views/res_company.xml",
        "views/payment_mode_view.xml",
        "views/account_tax_view.xml",
        "views/report_facturae.xml",
        "wizard/create_facturae_view.xml",
        "wizard/account_move_reversal_view.xml",
        "views/account_move_view.xml",
    ],
    "external_dependencies": {
        "python": ["cryptography", "pyOpenSSL", "pycountry", "xmlsig"]
    },
    "installable": True,
    "maintainers": ["etobella"],
}
<|endoftext|>


<commit_before># See README.rst file on addon root folder for license details

{
    "name": "Libro de IVA",
    "version": "11.0.3.0.1",
    "author": "PRAXYA, "
              "Eficent, "
              "Tecnativa, "
              "Odoo Community Association (OCA)",
    "website": "https://github.com/OCA/l10n-spain",
    "license": "AGPL-3",
    "category": "Accounting",
    "depends": [
        'account',
        'base_vat',
        'l10n_es',
        'l10n_es_aeat',
        'report_xlsx',
    ],
    'data': [
        'security/ir_rule.xml',
        'security/ir.model.access.csv',
        'data/aeat_vat_book_map_data.xml',
        'views/aeat_vat_book_map_view.xml',
        'views/l10n_es_vat_book.xml',
        'views/l10n_es_vat_book_line.xml',
        'views/l10n_es_vat_book_tax_summary.xml',
        'views/l10n_es_vat_book_summary.xml',
        'report/common_templates.xml',
        'report/report_views.xml',
        'report/vat_book_invoices_issued.xml',
        'report/vat_book_invoices_received.xml',
        'report/vat_book_xlsx.xml',
    ],
    "installable": True,
}
<commit_msg>l10n_es_vat_book 11.0.3.1.0<commit_after># See README.rst file on addon root folder for license details

{
    "name": "Libro de IVA",
    "version": "11.0.3.1.0",
    "author": "PRAXYA, "
              "Eficent, "
              "Tecnativa, "
              "Odoo Community Association (OCA)",
    "website": "https://github.com/OCA/l10n-spain",
    "license": "AGPL-3",
    "category": "Accounting",
    "depends": [
        'account',
        'base_vat',
        'l10n_es',
        'l10n_es_aeat',
        'report_xlsx',
    ],
    'data': [
        'security/ir_rule.xml',
        'security/ir.model.access.csv',
        'data/aeat_vat_book_map_data.xml',
        'views/aeat_vat_book_map_view.xml',
        'views/l10n_es_vat_book.xml',
        'views/l10n_es_vat_book_line.xml',
        'views/l10n_es_vat_book_tax_summary.xml',
        'views/l10n_es_vat_book_summary.xml',
        'report/common_templates.xml',
        'report/report_views.xml',
        'report/vat_book_invoices_issued.xml',
        'report/vat_book_invoices_received.xml',
        'report/vat_book_xlsx.xml',
    ],
    "installable": True,
}
<|endoftext|>


<commit_before>from Base_Interface import Base_Interface, InterfaceError, InterfaceTimeout
from Base_Resource import Base_Resource, ResourceNotOpen

import importlib
import sys
import time

import serial
import serial.tools.list_ports
# list(serial.tools.list_ports.comports())

import common.resource_status as resource_status
from serial.serialutil import SerialException

class i_Serial(Base_Interface):

    info = {
        # Interface Author
        'author':               'KKENNEDY',
        # Interface Version
        'version':              '1.0',
        # Revision date
        'date':                 '2015-03-06'
    }

    # Dict: ResID -> (VID, PID)
    resources = {}

    REFRESH_RATE = 5.0 # Seconds

    def open(self):
        return True

    def close(self):
        for resID, res in self.resources.items():
            try:
                if res.isOpen():
                    res.close()

                res.stop()

            except:
                pass

        return True

    def getResources(self):
        return self.resources

    #===========================================================================
    # Optional - Automatic Controllers
    #===========================================================================

    def refresh(self):
        """
        Scans system for new resources and creates resource objects for them.
        """
        try:
            res_list = list(serial.tools.list_ports.comports())

            # Check for new resources
            for res in res_list:
                resID, _, _ = res

                if resID not in self.resources:
                    new_resource = r_Serial(resID, self,
                                            logger=self.logger,
                                            config=self.config,
                                            enableRpc=self.manager.enableRpc)
                    self.resources[resID] = new_resource

                    self.manager._cb_new_resource()

        except:
            # Exception thrown when there are no resources
            self.logger.exception("Unhandled Exception occurred while creating new Serial resource: %s", resID)

    #===========================================================================
    #     if sys.platform.startswith('win'):
    #         ports = ['COM' + str(i + 1) for i in range(256)]
    #
    #     elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
    #         # this is to exclude your current terminal "/dev/tty"
    #         ports = glob.glob('/dev/tty[A-Za-z]*')
    #
    #     elif sys.platform.startswith('darwin'):
    #         ports = glob.glob('/dev/tty.*')
    #
    #     else:
    #         raise EnvironmentError('Unsupported platform')
    #
    #     result = []
    #     for port in ports:
    #         try:
    #             s = serial.Serial(port)
    #             s.close()
    #             self.resources[port] = ('', '')
    #             self.logger.debug('Found Serial Device %s', port)
    #         except (OSError, serial.SerialException):
    #             pass
    #===========================================================================

import common.status

class r_Serial(Base_Resource):
    type = "Serial"

    CR = '\r'
    LF = '\n'
    termination = CR + LF

    def __init__(self, resID, controller, **kwargs):
        Base_Resource.__init__(self, resID, controller, **kwargs)

        try:
            #conn = "\\.\%s" % resID
            self.instrument = serial.Serial(port=resID, timeout=0)

            self.logger.info("Identified new Serial resource: %s", resID)

            self.setResourceStatus(resource_status.READY)

            # Serial port is immediately opened on object creation
            self.instrument.close()

        except serial.SerialException as e:
            # Seems to be thrown on Windows systems
            self.setResourceStatus(common.status.error)
            self.setResourceError((e.errno, e.message))
            self.logger.error("Serial Error %s: %s", e.errno, e.message)

        except OSError as e:
            # Seems to be thrown on POSIX systems
            self.setResourceStatus(common.status.error)
            self.setResourceError((e.errno, e.strerror))
            self.logger.error("OS Error %s: %s", e.errno, e.strerror)

        except:
            self.logger.exception("Unhandled exception")

    #===========================================================================
    # Resource State
    #===========================================================================

    def open(self):
        self.instrument.open()

    def close(self):
        self.instrument.close()

    def lock(self):
        pass

    def unlock(self):
        pass

    #===========================================================================
    # Configuration
    #===========================================================================

    def configure(self, **kwargs):
        """
        Configure Serial port parameters for the resource.

        :param baudrate: Serial - Baudrate. Default 9600
        :param timeout: Read timeout
        :param bytesize: Serial - Number of bits per frame. Default 8.
        :param parity: Serial - Parity
        :param stopbits: Serial - Number of stop bits
        :param termination: Write termination
        """
        if 'baudrate' in kwargs:
            self.instrument.setBaudrate(int(kwargs.get('baudrate')))

        if 'timeout' in kwargs:
            self.instrument.setTimeout(float(kwargs.get('timeout')))

        if 'bytesize' in kwargs:
            self.instrument.setByteSize(int(kwargs.get('bytesize')))

        if 'parity' in kwargs:
            self.instrument.setParity(kwargs.get('parity'))

        if 'stopbits' in kwargs:
            self.instrument.setStopbits(int(kwargs.get('stopbits')))

        if 'termination' in kwargs:
            self.termination = kwargs.get('termination')

    def getConfiguration(self):
        return self.instrument.getSettingsDict()

    #===========================================================================
    # Data Transmission
    #===========================================================================

    def write(self, data):
        """
        Send String data to the instrument. Includes termination
        character.

        Raises exception if the resource is not ready.

        To get the error condition, call `getResourceError`

        :param data: Data to send
        :type data: str
        :raises: ResourceNotReady
        """
        try:
            self.logger.debug("Serial Write: %s", data)
            self.instrument.write(data + self.termination)

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def write_raw(self, data):
        """
        Send Binary-encoded data to the instrument. Termination character is
        not included

        :param data: Data to send
        :type data: str
        :raises: ResourceNotReady
        """
        try:
            self.instrument.write(data)

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def read(self, termination=None):
        """
        Read string data from the instrument.

        Reading stops when the device stops sending, or the termination
        characters sequence was detected.

        All line-ending characters are stripped from the end of the string.
        """
        try:
            ret = ''

            if termination is None:
                bytes = self.instrument.inWaiting()
                ret += self.instrument.read(bytes)

            else:
                ret += self.instrument.read(1)
                while ret[-1] != self.termination[-1] or self.instrument.inWaiting() == 0:
                    ret += self.instrument.read(1)
                ret = ret[:-len(self.termination)]

            return ret

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def read_raw(self, size=None):
        """
        Read Binary-encoded data from the instrument.

        No termination characters are stripped.

        :param size: Number of bytes to read
        :type size: int
        :returns: bytes
        """
        try:
            ret = bytes()

            if size is None:
                bytes = self.instrument.inWaiting()
                ret += self.instrument.read(bytes)

            else:
                ret += self.instrument.read(size)

            return ret

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def query(self, data, delay=None):
        """
        Retreive ASCII-encoded data from the device given a prompt.

        A combination of write(data) and read()

        :param data: Data to send
        :type data: str
        :param delay: delay (in seconds) between write and read operations.
        :type delay: float
        :returns: str
        :raises: ResourceNotOpen
        """
        self.write(data)
        if delay is not None:
            time.sleep(delay)
        return self.read()

    def inWaiting(self):
        """
        Return the number of bytes in the receive buffer

        :returns: int
        """
        return self.instrument.inWaiting()

        <commit_msg>Fixed bug where serial resources were not being closed properly, leaving zombie RPC server threads<commit_after>from Base_Interface import Base_Interface, InterfaceError, InterfaceTimeout
from Base_Resource import Base_Resource, ResourceNotOpen

import importlib
import sys
import time

import serial
import serial.tools.list_ports
# list(serial.tools.list_ports.comports())

import common.resource_status as resource_status
from serial.serialutil import SerialException

class i_Serial(Base_Interface):

    info = {
        # Interface Author
        'author':               'KKENNEDY',
        # Interface Version
        'version':              '1.0',
        # Revision date
        'date':                 '2015-03-06'
    }

    # Dict: ResID -> (VID, PID)
    resources = {}

    REFRESH_RATE = 5.0 # Seconds

    def open(self):
        return True

    def close(self):
        for resID, res in self.resources.items():
            try:
                if res.isOpen():
                    res.close()

            except:
                pass

            res.stop()

        return True

    def getResources(self):
        return self.resources

    #===========================================================================
    # Optional - Automatic Controllers
    #===========================================================================

    def refresh(self):
        """
        Scans system for new resources and creates resource objects for them.
        """
        try:
            res_list = list(serial.tools.list_ports.comports())

            # Check for new resources
            for res in res_list:
                resID, _, _ = res

                if resID not in self.resources:
                    new_resource = r_Serial(resID, self,
                                            logger=self.logger,
                                            config=self.config,
                                            enableRpc=self.manager.enableRpc)
                    self.resources[resID] = new_resource

                    self.manager._cb_new_resource()

        except:
            # Exception thrown when there are no resources
            self.logger.exception("Unhandled Exception occurred while creating new Serial resource: %s", resID)

    #===========================================================================
    #     if sys.platform.startswith('win'):
    #         ports = ['COM' + str(i + 1) for i in range(256)]
    #
    #     elif sys.platform.startswith('linux') or sys.platform.startswith('cygwin'):
    #         # this is to exclude your current terminal "/dev/tty"
    #         ports = glob.glob('/dev/tty[A-Za-z]*')
    #
    #     elif sys.platform.startswith('darwin'):
    #         ports = glob.glob('/dev/tty.*')
    #
    #     else:
    #         raise EnvironmentError('Unsupported platform')
    #
    #     result = []
    #     for port in ports:
    #         try:
    #             s = serial.Serial(port)
    #             s.close()
    #             self.resources[port] = ('', '')
    #             self.logger.debug('Found Serial Device %s', port)
    #         except (OSError, serial.SerialException):
    #             pass
    #===========================================================================

import common.status

class r_Serial(Base_Resource):
    type = "Serial"

    CR = '\r'
    LF = '\n'
    termination = CR + LF

    def __init__(self, resID, controller, **kwargs):
        Base_Resource.__init__(self, resID, controller, **kwargs)

        try:
            #conn = "\\.\%s" % resID
            self.instrument = serial.Serial(port=resID, timeout=0)

            self.logger.info("Identified new Serial resource: %s", resID)

            self.setResourceStatus(resource_status.READY)

            # Serial port is immediately opened on object creation
            self.instrument.close()

        except serial.SerialException as e:
            # Seems to be thrown on Windows systems
            self.setResourceStatus(common.status.error)
            self.setResourceError((e.errno, e.message))
            self.logger.error("Serial Error %s: %s", e.errno, e.message)

        except OSError as e:
            # Seems to be thrown on POSIX systems
            self.setResourceStatus(common.status.error)
            self.setResourceError((e.errno, e.strerror))
            self.logger.error("OS Error %s: %s", e.errno, e.strerror)

        except:
            self.logger.exception("Unhandled exception")

    #===========================================================================
    # Resource State
    #===========================================================================

    def open(self):
        self.instrument.open()

    def isOpen(self):
        return self.instrument._isOpen

    def close(self):
        self.instrument.close()

    def lock(self):
        pass

    def unlock(self):
        pass

    #===========================================================================
    # Configuration
    #===========================================================================

    def configure(self, **kwargs):
        """
        Configure Serial port parameters for the resource.

        :param baudrate: Serial - Baudrate. Default 9600
        :param timeout: Read timeout
        :param bytesize: Serial - Number of bits per frame. Default 8.
        :param parity: Serial - Parity
        :param stopbits: Serial - Number of stop bits
        :param termination: Write termination
        """
        if 'baudrate' in kwargs:
            self.instrument.setBaudrate(int(kwargs.get('baudrate')))

        if 'timeout' in kwargs:
            self.instrument.setTimeout(float(kwargs.get('timeout')))

        if 'bytesize' in kwargs:
            self.instrument.setByteSize(int(kwargs.get('bytesize')))

        if 'parity' in kwargs:
            self.instrument.setParity(kwargs.get('parity'))

        if 'stopbits' in kwargs:
            self.instrument.setStopbits(int(kwargs.get('stopbits')))

        if 'termination' in kwargs:
            self.termination = kwargs.get('termination')

    def getConfiguration(self):
        return self.instrument.getSettingsDict()

    #===========================================================================
    # Data Transmission
    #===========================================================================

    def write(self, data):
        """
        Send String data to the instrument. Includes termination
        character.

        Raises exception if the resource is not ready.

        To get the error condition, call `getResourceError`

        :param data: Data to send
        :type data: str
        :raises: ResourceNotReady
        """
        try:
            self.logger.debug("Serial Write: %s", data)
            self.instrument.write(data + self.termination)

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def write_raw(self, data):
        """
        Send Binary-encoded data to the instrument. Termination character is
        not included

        :param data: Data to send
        :type data: str
        :raises: ResourceNotReady
        """
        try:
            self.instrument.write(data)

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def read(self, termination=None):
        """
        Read string data from the instrument.

        Reading stops when the device stops sending, or the termination
        characters sequence was detected.

        All line-ending characters are stripped from the end of the string.
        """
        try:
            ret = ''

            if termination is None:
                bytes = self.instrument.inWaiting()
                ret += self.instrument.read(bytes)

            else:
                ret += self.instrument.read(1)
                while ret[-1] != self.termination[-1] or self.instrument.inWaiting() == 0:
                    ret += self.instrument.read(1)
                ret = ret[:-len(self.termination)]

            return ret

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def read_raw(self, size=None):
        """
        Read Binary-encoded data from the instrument.

        No termination characters are stripped.

        :param size: Number of bytes to read
        :type size: int
        :returns: bytes
        """
        try:
            ret = bytes()

            if size is None:
                bytes = self.instrument.inWaiting()
                ret += self.instrument.read(bytes)

            else:
                ret += self.instrument.read(size)

            return ret

        except SerialException as e:
            if e == serial.portNotOpenError:
                raise ResourceNotOpen
            else:
                raise InterfaceError

    def query(self, data, delay=None):
        """
        Retreive ASCII-encoded data from the device given a prompt.

        A combination of write(data) and read()

        :param data: Data to send
        :type data: str
        :param delay: delay (in seconds) between write and read operations.
        :type delay: float
        :returns: str
        :raises: ResourceNotOpen
        """
        self.write(data)
        if delay is not None:
            time.sleep(delay)
        return self.read()

    def inWaiting(self):
        """
        Return the number of bytes in the receive buffer

        :returns: int
        """
        return self.instrument.inWaiting()

        <|endoftext|>


<commit_before>from __future__ import print_function

import os
import sys

import numpy as np

from landlab.layers.eventlayers import (EventLayers,
                                        _deposit_or_erode,
                                        _get_surface_index)

class MaterialLayersMixIn(object):

    """MixIn that adds a MaterialLayers attribute to a ModelGrid."""

    @property
    def layers(self):
        """MaterialLayers for each cell."""
        try:
            self._layers
        except AttributeError:
            self._layers = MaterialLayers(self.number_of_cells)
        finally:
            return self._layers

class MaterialLayers(EventLayers):

    """Track MaterialLayers where each layer has some material in it.

    MaterialLayers are meant to represent a layered object in which each layer
    has some material in it. If erosion occurs, no new layer is created. These
    layers stand in contrast to the EventLayers for which each event is
    represented by a layer.

    MaterialLayers is likely a more memory efficent data structure than
    EventLayers as it does not record erosion as an array of zeros.

    Parameters
    ----------
    number_of_stacks : int
        Number of layer stacks to track.

    Attributes
    ----------
    allocated
    dz
    thickness
    number_of_layers
    number_of_stacks
    surface_index
    z

    Methods
    -------
    add
    get_surface_values

    Examples
    --------
    >>> from landlab.layers.materiallayers import MaterialLayers

    Create an empty layer stack with 5 stacks.

    >>> layers = MaterialLayers(5)
    >>> layers.number_of_stacks
    5
    >>> layers.number_of_layers
    0

    Add a layer with a uniform thickness.

    >>> layers.add(1.5)
    >>> layers.number_of_layers
    1
    >>> layers.dz
    array([[ 1.5,  1.5,  1.5,  1.5,  1.5]])

    MaterialLayers will combine layers if they have the same attributes.
    Adding a second layer with uneven thickness. Will increment the
    first layers thickness. This stands in contrast with EventLayers
    which will track each addition as a separate entry in the layers
    datastructure.

    >>> layers.add([1., 2., 3., 5., 0.])
    >>> layers.dz
    array([[ 2.5,  3.5,  4.5,  6.5,  1.5]])

    Adding a layer with negative thickness will remove
    material from the layers. Unlike EventLayers, it will not add a
    layer of zeros that represent an event with no deposition.

    >>> layers.add(-1)
    >>> layers.dz
    array([[ 1.5,  2.5,  3.5,  5.5,  0.5]])

    Get the index value of the layer within each stack
    at the topographic surface.

    >>> layers.surface_index
    array([0, 0, 0, 0, 0])

    See the example in the ``add`` method to learn how MaterialLayers
    behaves if material properties are also tracked.
    """

    def __init__(self, number_of_stacks, allocated=0):
        super(MaterialLayers, self).__init__(number_of_stacks, allocated=allocated)

    def add(self, dz, **kwds):
        """Add a layer to the  MaterialLayers stacks.

        Parameters
        ----------
        dz : float or array_like
            Thickness to add to each stack.

        Examples
        --------
        >>> from landlab.layers.materiallayers import MaterialLayers

        Create an empty layer stack with 3 stacks.

        >>> layers = MaterialLayers(3)
        >>> layers.number_of_layers
        0

        To add a layer of uniform thickness to every stack.

        >>> layers.add(1.5)
        >>> layers.number_of_layers
        1
        >>> layers.dz
        array([[ 1.5,  1.5,  1.5]])

        Add a second layer with uneven thickness.

        >>> layers.add([1., 2., .5])
        >>> layers.dz
        array([[ 2.5,  3.5,  2. ]])

        Because the attributes of this layer and the previous layer
        are the same (e.g. they don't exist), MaterialLayer will combined
        them. This is the primary difference between MaterialLayers and
        EventLayers.

        Adding a layer with negative thickness will remove material from
        the top of the stack.

        >>> layers.add(-1)
        >>> layers.dz
        array([[ 1.5,  2.5,  1. ]])
        >>> layers.number_of_layers
        1

        Use keywords to track properties of each layer. For instance,
        here we create a new stack and add a layer with a particular
        *age*. You can access the layer properties as if the object
        were a dictionary.

        >>> layers = MaterialLayers(3)
        >>> layers.add(1., age=3.)
        >>> layers.dz
        array([[ 1.,  1.,  1.]])
        >>> layers['age']
        array([[ 3.,  3.,  3.]])
        >>> layers.add(2., age=6.)
        >>> layers['age']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.]])

        Attributes for each layer will exist even if the the layer is
        associated with erosion.

        >>> layers.add([-2, -1, 1], age=8.)
        >>> layers.dz
        array([[ 1.,  1.,  1.],
               [ 0.,  1.,  2.],
               [ 0.,  0.,  1.]])
        >>> layers['age']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.],
               [ 8.,  8.,  8.]])

        To get the values at the surface of the layer stack:

        >>> layers.get_surface_values('age')
        array([ 3.,  6.,  8.])

        Removing enough material such that an entire layer's
        thickness is no longer present, results in that layer
        no longer being tracked. This is another difference
        between MaterialLayers and EventLayers.

        >>> layers.add([ 0., 0., -1. ])
        >>> layers.dz
        array([[ 1.,  1.,  1.],
               [ 0.,  1.,  2.]])
        >>> layers['age']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.]])
        >>> layers.number_of_layers
        2
        """
        if self.number_of_layers == 0:
            self._setup_layers(**kwds)

        # if deposition will occur
        if np.any(np.asarray(dz)>0.0):

            # check if the deposit can be combined with the current surface
            # deposit.

            # short circuit this if no layers exist yet.
            if self.number_of_layers == 0:
                #print('first deposit!')
                compatible = False
            else:
                # start by getting the current surface index.
                compatible = True
                _get_surface_index(self._attrs['_dz'], self.number_of_layers, self._surface_index)
                #print(self._surface_index)
                # for each type stored attribute, check if the current surface
                # value and the new value are the same. Once one value is
                # incompatible, break and consider incompatible.
                for name in kwds:
                    current_surface_values = self[name][self._surface_index,
                                                        np.arange(self._number_of_stacks)]
                    new_layer_values = kwds[name]
                    if np.array_equiv(current_surface_values, new_layer_values):
                        pass
                    else:
                        compatible = False
                        break
            # if the layers are compatible, just increment the value of the
            # layer thickness for the deposit parts of dz, and then erode the
            # negative parts of dt.

            if compatible:
                #print('compatible')
                if np.asarray(dz).size == 1:
                    # if size is one, we don't need to worry about the
                    # pos and negative parts.
                    #print('adding')

                    self._attrs['_dz'][self._surface_index,
                                       np.arange(self._number_of_stacks)] += np.asarray(dz)
                else:
                    #print('more complicated')
                    positive_part = np.asarray(dz).copy()
                    positive_part[positive_part<0] = 0.0
                    self._attrs['_dz'][self._surface_index,
                                       np.arange(self._number_of_stacks)] += np.asarray(positive_part)

                    negative_part = np.asarray(dz).copy()
                    negative_part[negative_part>0] = 0.0
                    self._erode(negative_part)
            # if a new layer is needed, then add an empty layer and track
            # attributes
            else:
                #print('incompatible')
                self._add_empty_layer()
                _deposit_or_erode(self._attrs['_dz'], self.number_of_layers, dz)

                for name in kwds:
                    try:
                        self[name][-1] = kwds[name]
                    except KeyError:
                        print('{0} is not being tracked. Ignoring'.format(name),
                              file=sys.stderr)

        # if no deposition will occur, then do not create new layer, erode,
        # and do not track properties.
        else:
            #print('just eroding')
            self._erode(dz)

    def _erode(self, dz):
        """ """
        _deposit_or_erode(self._attrs['_dz'], self.number_of_layers+1, dz)
        _get_surface_index(self._attrs['_dz'], self.number_of_layers+1, self._surface_index)
        if np.all((self._surface_index + 1) < self.number_of_layers):
            self._number_of_layers = np.max(self._surface_index) + 1
<commit_msg>making material layers better tested with new compatibility methods<commit_after>from __future__ import print_function

import os
import sys

import numpy as np

from landlab.layers.eventlayers import (EventLayers,
                                        _deposit_or_erode,
                                        _get_surface_index)

class MaterialLayersMixIn(object):

    """MixIn that adds a MaterialLayers attribute to a ModelGrid."""

    @property
    def layers(self):
        """MaterialLayers for each cell."""
        try:
            self._layers
        except AttributeError:
            self._layers = MaterialLayers(self.number_of_cells)
        finally:
            return self._layers

class MaterialLayers(EventLayers):

    """Track MaterialLayers where each layer has some material in it.

    MaterialLayers are meant to represent a layered object in which each layer
    has some material in it. If erosion occurs, no new layer is created. These
    layers stand in contrast to the EventLayers for which each event is
    represented by a layer.

    MaterialLayers is likely a more memory efficent data structure than
    EventLayers as it does not record erosion as an array of zeros.

    Parameters
    ----------
    number_of_stacks : int
        Number of layer stacks to track.

    Attributes
    ----------
    allocated
    dz
    thickness
    number_of_layers
    number_of_stacks
    surface_index
    z

    Methods
    -------
    add
    get_surface_values

    Examples
    --------
    >>> from landlab.layers.materiallayers import MaterialLayers

    Create an empty layer stack with 5 stacks.

    >>> layers = MaterialLayers(5)
    >>> layers.number_of_stacks
    5
    >>> layers.number_of_layers
    0

    Add a layer with a uniform thickness.

    >>> layers.add(1.5)
    >>> layers.number_of_layers
    1
    >>> layers.dz
    array([[ 1.5,  1.5,  1.5,  1.5,  1.5]])

    MaterialLayers will combine layers if they have the same attributes.
    Adding a second layer with uneven thickness. Will increment the
    first layers thickness. This stands in contrast with EventLayers
    which will track each addition as a separate entry in the layers
    datastructure.

    >>> layers.add([1., 2., 3., 5., 0.])
    >>> layers.dz
    array([[ 2.5,  3.5,  4.5,  6.5,  1.5]])

    Adding a layer with negative thickness will remove
    material from the layers. Unlike EventLayers, it will not add a
    layer of zeros that represent an event with no deposition.

    >>> layers.add(-1)
    >>> layers.dz
    array([[ 1.5,  2.5,  3.5,  5.5,  0.5]])

    Get the index value of the layer within each stack
    at the topographic surface.

    >>> layers.surface_index
    array([0, 0, 0, 0, 0])

    See the example in the ``add`` method to learn how MaterialLayers
    behaves if material properties are also tracked.
    """

    def __init__(self, number_of_stacks, allocated=0):
        super(MaterialLayers, self).__init__(number_of_stacks, allocated=allocated)

    def add(self, dz, **kwds):
        """Add a layer to the  MaterialLayers stacks.

        Parameters
        ----------
        dz : float or array_like
            Thickness to add to each stack.

        Examples
        --------
        >>> from landlab.layers.materiallayers import MaterialLayers

        Create an empty layer stack with 3 stacks.

        >>> layers = MaterialLayers(3)
        >>> layers.number_of_layers
        0

        To add a layer of uniform thickness to every stack.

        >>> layers.add(1.5)
        >>> layers.number_of_layers
        1
        >>> layers.dz
        array([[ 1.5,  1.5,  1.5]])

        Add a second layer with uneven thickness.

        >>> layers.add([1., 2., .5])
        >>> layers.dz
        array([[ 2.5,  3.5,  2. ]])

        Because the attributes of this layer and the previous layer
        are the same (e.g. they don't exist), MaterialLayer will combined
        them. This is the primary difference between MaterialLayers and
        EventLayers.

        Adding a layer with negative thickness will remove material from
        the top of the stack.

        >>> layers.add(-1)
        >>> layers.dz
        array([[ 1.5,  2.5,  1. ]])
        >>> layers.number_of_layers
        1

        Use keywords to track properties of each layer. For instance,
        here we create a new stack and add a layer with a particular
        *type* and a particular *size*. You can access the layer properties as
        if the object were a dictionary.

        >>> layers = MaterialLayers(3)
        >>> layers.add(1., type=3., size='sand')
        >>> layers.dz
        array([[ 1.,  1.,  1.]])
        >>> layers['type']
        array([[ 3.,  3.,  3.]])

        As you can see, there is no rule that says you can't use a string as
        the value of an attribute.

        Adding a layer with the same attributes as the entire surface of the
        MaterialLayers will result in the layers being combined.

        >>> layers.add(1., type=3., size='sand')
        >>> layers.add([2, -1, 0], type=3., size='sand')
        >>> layers.dz
        array([[ 4.,  1.,  2.]])

        Adding material with different attributes results in the creation of
        a new layer.

        >>> layers.add(2., type=6., size='sand')
        >>> layers.dz
        array([[ 4.,  1.,  2.],
               [ 2.,  2.,  2.]])
        >>> layers['type']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.]])
        >>> layers['size']
        array([['sand', 'sand', 'sand'],
               ['sand', 'sand', 'sand']],
          dtype='<U4')

        Attributes for each layer will exist even if part the the layer is
        associated with erosion.

        >>> layers.add([-2, -1, 1], type=8., size='gravel')
        >>> layers.dz
        array([[ 4.,  1.,  2.],
               [ 0.,  1.,  2.],
               [ 0.,  0.,  1.]])
        >>> layers['type']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.],
               [ 8.,  8.,  8.]])

        To get the values at the surface of the layer stack:

        >>> layers.get_surface_values('type')
        array([ 3.,  6.,  8.])

        Removing enough material such that an entire layer's
        thickness is no longer present, results in that layer
        no longer being tracked. This is another difference
        between MaterialLayers and EventLayers.

        >>> layers.add([ 0., 0., -1. ])
        >>> layers.dz
        array([[ 4.,  1.,  2.],
               [ 0.,  1.,  2.]])
        >>> layers['type']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.]])
        >>> layers['size']
        array([['sand', 'sand', 'sand'],
               ['sand', 'sand', 'sand']],
               dtype='<U4')
        >>> layers.number_of_layers
        2

        If attributes (like age and size in this example) are tracked, a layer
        will be combined with the surface layer only if all attributes are the
        same across the entire layer. Right now, the surface values vary.

        >>> layers.get_surface_values('type')
        array([ 3.,  6.,  6.])
        >>> layers.get_surface_values('size')
        array(['sand', 'sand', 'sand'],
              dtype='<U4')

        Since the surface has different types, adding material will create a
        new layer.

        >>> layers.add(3., type=6., size='sand')
        >>> layers.dz
        array([[ 4.,  1.,  2.],
               [ 0.,  1.,  2.],
               [ 3.,  3.,  3.]])
        >>> layers['type']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.],
               [ 6.,  6.,  6.]])
        >>> layers.number_of_layers
        3

        But now, the entire surface has the qualities of type = 6. and size =
        'sand', so layers will be combined. This even works if the thickness of
        the new layer includes both erosion and deposition.

        >>> layers.add([ -3.5, 0., 2. ], type=6., size='sand')
        >>> layers.dz
        array([[ 3.5,  1. ,  2. ],
               [ 0. ,  1. ,  2. ],
               [ 0. ,  3. ,  5. ]])
        >>> layers['type']
        array([[ 3.,  3.,  3.],
               [ 6.,  6.,  6.],
               [ 6.,  6.,  6.]])
        >>> layers.number_of_layers
        3
        """
        if self.number_of_layers == 0:
            self._setup_layers(**kwds)

        # if deposition will occur
        if np.any(np.asarray(dz)>0.0):

            # check if the deposit can be combined with the current surface
            # deposit.

            # short circuit this if no layers exist yet.
            if self.number_of_layers == 0:
                #print('first deposit!')
                compatible = False
            else:
                # start by getting the current surface index.
                compatible = True
                _get_surface_index(self._attrs['_dz'], self.number_of_layers, self._surface_index)
                #print(self._surface_index)
                # for each type stored attribute, check if the current surface
                # value and the new value are the same. Once one value is
                # incompatible, break and consider incompatible.
                for name in kwds:
                    current_surface_values = self[name][self._surface_index,
                                                        np.arange(self._number_of_stacks)]
                    new_layer_values = kwds[name]
                    if np.array_equiv(current_surface_values, new_layer_values):
                        pass
                    else:
                        compatible = False
                        break
            # if the layers are compatible, just increment the value of the
            # layer thickness for the deposit parts of dz, and then erode the
            # negative parts of dt.

            if compatible:
                #print('compatible')
                if np.asarray(dz).size == 1:
                    # if size is one, we don't need to worry about the
                    # pos and negative parts.
                    #print('adding')

                    self._attrs['_dz'][self._surface_index,
                                       np.arange(self._number_of_stacks)] += np.asarray(dz)
                else:
                    #print('more complicated')
                    positive_part = np.asarray(dz).copy()
                    positive_part[positive_part<0] = 0.0
                    self._attrs['_dz'][self._surface_index,
                                       np.arange(self._number_of_stacks)] += np.asarray(positive_part)

                    negative_part = np.asarray(dz).copy()
                    negative_part[negative_part>0] = 0.0
                    self._erode(negative_part)
            # if a new layer is needed, then add an empty layer and track
            # attributes
            else:
                #print('incompatible')
                self._add_empty_layer()
                _deposit_or_erode(self._attrs['_dz'], self.number_of_layers, dz)

                for name in kwds:
                    try:
                        self[name][-1] = kwds[name]
                    except KeyError:
                        print('{0} is not being tracked. Ignoring'.format(name),
                              file=sys.stderr)

        # if no deposition will occur, then do not create new layer, erode,
        # and do not track properties.
        else:
            #print('just eroding')
            self._erode(dz)

    def _erode(self, dz):
        """ """
        _deposit_or_erode(self._attrs['_dz'], self.number_of_layers+1, dz)
        _get_surface_index(self._attrs['_dz'], self.number_of_layers+1, self._surface_index)
        if np.all((self._surface_index + 1) < self.number_of_layers):
            self._number_of_layers = np.max(self._surface_index) + 1
<|endoftext|>


<commit_before>import warnings

from ..base import BaseEstimator, TransformerMixin
from ..utils import check_array
from ..utils.validation import _allclose_dense_sparse

def _identity(X):
    """The identity function.
    """
    return X

class FunctionTransformer(TransformerMixin, BaseEstimator):
    """Constructs a transformer from an arbitrary callable.

    A FunctionTransformer forwards its X (and optionally y) arguments to a
    user-defined function or function object and returns the result of this
    function. This is useful for stateless transformations such as taking the
    log of frequencies, doing custom scaling, etc.

    Note: If a lambda is used as the function, then the resulting
    transformer will not be pickleable.

    .. versionadded:: 0.17

    Read more in the :ref:`User Guide <function_transformer>`.

    Parameters
    ----------
    func : callable, optional default=None
        The callable to use for the transformation. This will be passed
        the same arguments as transform, with args and kwargs forwarded.
        If func is None, then func will be the identity function.

    inverse_func : callable, optional default=None
        The callable to use for the inverse transformation. This will be
        passed the same arguments as inverse transform, with args and
        kwargs forwarded. If inverse_func is None, then inverse_func
        will be the identity function.

    validate : bool, optional default=False
        Indicate that the input X array should be checked before calling
        ``func``. The possibilities are:

        - If False, there is no input validation.
        - If True, then X will be converted to a 2-dimensional NumPy array or
          sparse matrix. If the conversion is not possible an exception is
          raised.

        .. deprecated:: 0.22
           The default of ``validate`` changed from True to False.

    accept_sparse : boolean, optional
        Indicate that func accepts a sparse matrix as input. If validate is
        False, this has no effect. Otherwise, if accept_sparse is false,
        sparse matrix inputs will cause an exception to be raised.

    check_inverse : bool, default=True
       Whether to check that or ``func`` followed by ``inverse_func`` leads to
       the original inputs. It can be used for a sanity check, raising a
       warning when the condition is not fulfilled.

       .. versionadded:: 0.20

    kw_args : dict, optional
        Dictionary of additional keyword arguments to pass to func.

    inv_kw_args : dict, optional
        Dictionary of additional keyword arguments to pass to inverse_func.

    """
    def __init__(self, func=None, inverse_func=None, validate=False,
                 accept_sparse=False, check_inverse=True, kw_args=None,
                 inv_kw_args=None):
        self.func = func
        self.inverse_func = inverse_func
        self.validate = validate
        self.accept_sparse = accept_sparse
        self.check_inverse = check_inverse
        self.kw_args = kw_args
        self.inv_kw_args = inv_kw_args

    def _check_input(self, X):
        if self.validate:
            return check_array(X, accept_sparse=self.accept_sparse)
        return X

    def _check_inverse_transform(self, X):
        """Check that func and inverse_func are the inverse."""
        idx_selected = slice(None, None, max(1, X.shape[0] // 100))
        X_round_trip = self.inverse_transform(self.transform(X[idx_selected]))
        if not _allclose_dense_sparse(X[idx_selected], X_round_trip):
            warnings.warn("The provided functions are not strictly"
                          " inverse of each other. If you are sure you"
                          " want to proceed regardless, set"
                          " 'check_inverse=False'.", UserWarning)

    def fit(self, X, y=None):
        """Fit transformer by checking X.

        If ``validate`` is ``True``, ``X`` will be checked.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_features)
            Input array.

        Returns
        -------
        self
        """
        X = self._check_input(X)
        if (self.check_inverse and not (self.func is None or
                                        self.inverse_func is None)):
            self._check_inverse_transform(X)
        return self

    def transform(self, X):
        """Transform X using the forward function.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_features)
            Input array.

        Returns
        -------
        X_out : array-like, shape (n_samples, n_features)
            Transformed input.
        """
        return self._transform(X, func=self.func, kw_args=self.kw_args)

    def inverse_transform(self, X):
        """Transform X using the inverse function.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_features)
            Input array.

        Returns
        -------
        X_out : array-like, shape (n_samples, n_features)
            Transformed input.
        """
        return self._transform(X, func=self.inverse_func,
                               kw_args=self.inv_kw_args)

    def _transform(self, X, func=None, kw_args=None):
        X = self._check_input(X)

        if func is None:
            func = _identity

        return func(X, **(kw_args if kw_args else {}))

    def _more_tags(self):
        return {'no_validation': not self.validate,
                'stateless': True}
<commit_msg>DOC Adds example for preprocessing.FunctionTransformer (#15192)<commit_after>import warnings

from ..base import BaseEstimator, TransformerMixin
from ..utils import check_array
from ..utils.validation import _allclose_dense_sparse

def _identity(X):
    """The identity function.
    """
    return X

class FunctionTransformer(TransformerMixin, BaseEstimator):
    """Constructs a transformer from an arbitrary callable.

    A FunctionTransformer forwards its X (and optionally y) arguments to a
    user-defined function or function object and returns the result of this
    function. This is useful for stateless transformations such as taking the
    log of frequencies, doing custom scaling, etc.

    Note: If a lambda is used as the function, then the resulting
    transformer will not be pickleable.

    .. versionadded:: 0.17

    Read more in the :ref:`User Guide <function_transformer>`.

    Parameters
    ----------
    func : callable, optional default=None
        The callable to use for the transformation. This will be passed
        the same arguments as transform, with args and kwargs forwarded.
        If func is None, then func will be the identity function.

    inverse_func : callable, optional default=None
        The callable to use for the inverse transformation. This will be
        passed the same arguments as inverse transform, with args and
        kwargs forwarded. If inverse_func is None, then inverse_func
        will be the identity function.

    validate : bool, optional default=False
        Indicate that the input X array should be checked before calling
        ``func``. The possibilities are:

        - If False, there is no input validation.
        - If True, then X will be converted to a 2-dimensional NumPy array or
          sparse matrix. If the conversion is not possible an exception is
          raised.

        .. deprecated:: 0.22
           The default of ``validate`` changed from True to False.

    accept_sparse : boolean, optional
        Indicate that func accepts a sparse matrix as input. If validate is
        False, this has no effect. Otherwise, if accept_sparse is false,
        sparse matrix inputs will cause an exception to be raised.

    check_inverse : bool, default=True
       Whether to check that or ``func`` followed by ``inverse_func`` leads to
       the original inputs. It can be used for a sanity check, raising a
       warning when the condition is not fulfilled.

       .. versionadded:: 0.20

    kw_args : dict, optional
        Dictionary of additional keyword arguments to pass to func.

    inv_kw_args : dict, optional
        Dictionary of additional keyword arguments to pass to inverse_func.

    Examples
    --------
    >>> import numpy as np
    >>> from sklearn.preprocessing import FunctionTransformer
    >>> transformer = FunctionTransformer(np.log1p)
    >>> X = np.array([[0, 1], [2, 3]])
    >>> transformer.transform(X)
    array([[0.       , 0.6931...],
           [1.0986..., 1.3862...]])
    """
    def __init__(self, func=None, inverse_func=None, validate=False,
                 accept_sparse=False, check_inverse=True, kw_args=None,
                 inv_kw_args=None):
        self.func = func
        self.inverse_func = inverse_func
        self.validate = validate
        self.accept_sparse = accept_sparse
        self.check_inverse = check_inverse
        self.kw_args = kw_args
        self.inv_kw_args = inv_kw_args

    def _check_input(self, X):
        if self.validate:
            return check_array(X, accept_sparse=self.accept_sparse)
        return X

    def _check_inverse_transform(self, X):
        """Check that func and inverse_func are the inverse."""
        idx_selected = slice(None, None, max(1, X.shape[0] // 100))
        X_round_trip = self.inverse_transform(self.transform(X[idx_selected]))
        if not _allclose_dense_sparse(X[idx_selected], X_round_trip):
            warnings.warn("The provided functions are not strictly"
                          " inverse of each other. If you are sure you"
                          " want to proceed regardless, set"
                          " 'check_inverse=False'.", UserWarning)

    def fit(self, X, y=None):
        """Fit transformer by checking X.

        If ``validate`` is ``True``, ``X`` will be checked.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_features)
            Input array.

        Returns
        -------
        self
        """
        X = self._check_input(X)
        if (self.check_inverse and not (self.func is None or
                                        self.inverse_func is None)):
            self._check_inverse_transform(X)
        return self

    def transform(self, X):
        """Transform X using the forward function.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_features)
            Input array.

        Returns
        -------
        X_out : array-like, shape (n_samples, n_features)
            Transformed input.
        """
        return self._transform(X, func=self.func, kw_args=self.kw_args)

    def inverse_transform(self, X):
        """Transform X using the inverse function.

        Parameters
        ----------
        X : array-like, shape (n_samples, n_features)
            Input array.

        Returns
        -------
        X_out : array-like, shape (n_samples, n_features)
            Transformed input.
        """
        return self._transform(X, func=self.inverse_func,
                               kw_args=self.inv_kw_args)

    def _transform(self, X, func=None, kw_args=None):
        X = self._check_input(X)

        if func is None:
            func = _identity

        return func(X, **(kw_args if kw_args else {}))

    def _more_tags(self):
        return {'no_validation': not self.validate,
                'stateless': True}
<|endoftext|>