<commit_before># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
from operator import attrgetter, itemgetter

from osv import osv, fields
from tools.translate import _
import netsvc
from tools import ustr
import pooler

class res_config_configurable(osv.osv_memory):
    ''' Base classes for new-style configuration items

    Configuration items should inherit from this class, implement
    the execute method (and optionally the cancel one) and have
    their view inherit from the related res_config_view_base view.
    '''
    _name = 'res.config'
    _inherit = 'ir.wizard.screen'
    __logger = logging.getLogger(_name)

    def get_current_progress(self, cr, uid, context=None):
        '''Return a description the current progress of configuration:
        a tuple of (non_open_todos:int, total_todos: int)
        '''
        return itemgetter('done', 'total')(
            self.pool.get('ir.actions.todo').progress(cr, uid, context=context))

    def _progress(self, cr, uid, context=None):
        closed, total = self.get_current_progress(cr, uid, context=context)
        if total:
            return round(closed*100./total)
        return 100.

    _columns = dict(
        progress = fields.float('Configuration Progress', readonly=True),
    )

    _defaults = dict(
        progress = _progress,
    )

    def _next_action(self, cr, uid, context=None):
        Todos = self.pool['ir.actions.todo']
        self.__logger.info('getting next %s', Todos)

        active_todos = Todos.browse(cr, uid,
            Todos.search(cr, uid, ['&', ('type', '=', 'special'), ('state','=','open')]),
                                    context=context)

        user_groups = set(map(
            lambda g: g.id,
            self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))

        valid_todos_for_user = [
            todo for todo in active_todos
            if not todo.groups_id or bool(user_groups.intersection((
                group.id for group in todo.groups_id)))
        ]

        if valid_todos_for_user:
            return valid_todos_for_user[0]

        return None

    def _next(self, cr, uid, context=None):
        self.__logger.info('getting next operation')
        next = self._next_action(cr, uid, context=context)
        self.__logger.info('next action is %s', next)
        if next:
            res = next.action_launch(context=context)
            res['nodestroy'] = False
            return res
        self.__logger.info('all configuration actions have been executed')

        current_user_menu = self.pool.get('res.users').browse(cr, uid, uid).menu_id
        # return the action associated with the menu
        return self.pool.get(current_user_menu.type).read(cr, uid, current_user_menu.id)

    def start(self, cr, uid, ids, context=None):
        todo_pool = self.pool.get('ir.actions.todo')
        ids2 = todo_pool.search(cr, uid, [], context=context)
        for todo in todo_pool.browse(cr, uid, ids2, context=context):
            if todo.type == 'recurring':
                todo.write({'state':'open'})
        return self.next(cr, uid, ids, context)

    def next(self, cr, uid, ids, context=None):
        """ Returns the next todo action to execute (using the default
        sort order)
        """
        return self._next(cr, uid, context=context)

    def execute(self, cr, uid, ids, context=None):
        """ Method called when the user clicks on the ``Next`` button.

        Execute *must* be overloaded unless ``action_next`` is overloaded
        (which is something you generally don't need to do).

        If ``execute`` returns an action dictionary, that action is executed
        rather than just going to the next configuration item.
        """
        raise NotImplementedError(
            'Configuration items need to implement execute')
    def cancel(self, cr, uid, ids, context=None):
        """ Method called when the user click on the ``Skip`` button.

        ``cancel`` should be overloaded instead of ``action_skip``. As with
        ``execute``, if it returns an action dictionary that action is
        executed in stead of the default (going to the next configuration item)

        The default implementation is a NOOP.

        ``cancel`` is also called by the default implementation of
        ``action_cancel``.
        """
        pass

    def action_next(self, cr, uid, ids, context=None):
        """ Action handler for the ``next`` event.

        Sets the status of the todo the event was sent from to
        ``done``, calls ``execute`` and -- unless ``execute`` returned
        an action dictionary -- executes the action provided by calling
        ``next``.
        """
        next = self.execute(cr, uid, ids, context=context)
        if next: return next
        return self.next(cr, uid, ids, context=context)

    def action_skip(self, cr, uid, ids, context=None):
        """ Action handler for the ``skip`` event.

        Sets the status of the todo the event was sent from to
        ``skip``, calls ``cancel`` and -- unless ``cancel`` returned
        an action dictionary -- executes the action provided by calling
        ``next``.
        """
        next = self.cancel(cr, uid, ids, context=context)
        if next: return next
        return self.next(cr, uid, ids, context=context)

    def action_cancel(self, cr, uid, ids, context=None):
        """ Action handler for the ``cancel`` event. That event isn't
        generated by the res.config.view.base inheritable view, the
        inherited view has to overload one of the buttons (or add one
        more).

        Sets the status of the todo the event was sent from to
        ``cancel``, calls ``cancel`` and -- unless ``cancel`` returned
        an action dictionary -- executes the action provided by calling
        ``next``.
        """
        next = self.cancel(cr, uid, ids, context=context)
        if next: return next
        return self.next(cr, uid, ids, context=context)

res_config_configurable()

class res_config_installer(osv.osv_memory):
    """ New-style configuration base specialized for addons selection
    and installation.

    Basic usage
    -----------

    Subclasses can simply define a number of _columns as
    fields.boolean objects. The keys (column names) should be the
    names of the addons to install (when selected). Upon action
    execution, selected boolean fields (and those only) will be
    interpreted as addons to install, and batch-installed.

    Additional addons
    -----------------

    It is also possible to require the installation of an additional
    addon set when a specific preset of addons has been marked for
    installation (in the basic usage only, additionals can't depend on
    one another).

    These additionals are defined through the ``_install_if``
    property. This property is a mapping of a collection of addons (by
    name) to a collection of addons (by name) [#]_, and if all the *key*
    addons are selected for installation, then the *value* ones will
    be selected as well. For example::

        _install_if = {
            ('sale','crm'): ['sale_crm'],
        }

    This will install the ``sale_crm`` addon if and only if both the
    ``sale`` and ``crm`` addons are selected for installation.

    You can define as many additionals as you wish, and additionals
    can overlap in key and value. For instance::

        _install_if = {
            ('sale','crm'): ['sale_crm'],
            ('sale','project'): ['project_mrp'],
        }

    will install both ``sale_crm`` and ``project_mrp`` if all of
    ``sale``, ``crm`` and ``project`` are selected for installation.

    Hook methods
    ------------

    Subclasses might also need to express dependencies more complex
    than that provided by additionals. In this case, it's possible to
    define methods of the form ``_if_%(name)s`` where ``name`` is the
    name of a boolean field. If the field is selected, then the
    corresponding module will be marked for installation *and* the
    hook method will be executed.

    Hook methods take the usual set of parameters (cr, uid, ids,
    context) and can return a collection of additional addons to
    install (if they return anything, otherwise they should not return
    anything, though returning any "falsy" value such as None or an
    empty collection will have the same effect).

    Complete control
    ----------------

    The last hook is to simply overload the ``modules_to_install``
    method, which implements all the mechanisms above. This method
    takes the usual set of parameters (cr, uid, ids, context) and
    returns a ``set`` of addons to install (addons selected by the
    above methods minus addons from the *basic* set which are already
    installed) [#]_ so an overloader can simply manipulate the ``set``
    returned by ``res_config_installer.modules_to_install`` to add or
    remove addons.

    Skipping the installer
    ----------------------

    Unless it is removed from the view, installers have a *skip*
    button which invokes ``action_skip`` (and the ``cancel`` hook from
    ``res.config``). Hooks and additionals *are not run* when skipping
    installation, even for already installed addons.

    Again, setup your hooks accordingly.

    .. [#] note that since a mapping key needs to be hashable, it's
           possible to use a tuple or a frozenset, but not a list or a
           regular set

    .. [#] because the already-installed modules are only pruned at
           the very end of ``modules_to_install``, additionals and
           hooks depending on them *are guaranteed to execute*. Setup
           your hooks accordingly.
    """
    _name = 'res.config.installer'
    _inherit = 'res.config'
    __logger = logging.getLogger(_name)

    _install_if = {}

    def _already_installed(self, cr, uid, context=None):
        """ For each module (boolean fields in a res.config.installer),
        check if it's already installed (either 'to install', 'to upgrade' or 'installed')
        and if it is, check it by default
        """
        modules = self.pool.get('ir.module.module')

        selectable = [field for field in self._columns
                      if type(self._columns[field]) is fields.boolean]
        return modules.browse(
            cr, uid,
            modules.search(cr, uid,
                           [('name','in',selectable),
                            ('state','in',['to install', 'installed', 'to upgrade'])],
                           context=context),
            context=context)

    def modules_to_install(self, cr, uid, ids, context=None):
        """ selects all modules to install:

        * checked boolean fields
        * return values of hook methods. Hook methods are of the form
          ``_if_%(addon_name)s``, and are called if the corresponding
          addon is marked for installation. They take the arguments
          cr, uid, ids and context, and return an iterable of addon
          names
        * additionals, additionals are setup through the ``_install_if``
          class variable. ``_install_if`` is a dict of {iterable:iterable}
          where key and value are iterables of addon names.

          If all the addons in the key are selected for installation
          (warning: addons added through hooks don't count), then the
          addons in the value are added to the set of modules to install
        * not already installed
        """
        base = set(module_name
                   for installer in self.read(cr, uid, ids, context=context)
                   for module_name, to_install in installer.iteritems()
                   if module_name != 'id'
                   if type(self._columns[module_name]) is fields.boolean
                   if to_install)

        hooks_results = set()
        for module in base:
            hook = getattr(self, '_if_%s'%(module), None)
            if hook:
                hooks_results.update(hook(cr, uid, ids, context=None) or set())

        additionals = set(
            module for requirements, consequences \
                       in self._install_if.iteritems()
                   if base.issuperset(requirements)
                   for module in consequences)

        return (base | hooks_results | additionals) - set(
            map(attrgetter('name'), self._already_installed(cr, uid, context)))

    def default_get(self, cr, uid, fields_list, context=None):
        ''' If an addon is already installed, check it by default
        '''
        defaults = super(res_config_installer, self).default_get(
            cr, uid, fields_list, context=context)

        return dict(defaults,
                    **dict.fromkeys(
                        map(attrgetter('name'),
                            self._already_installed(cr, uid, context=context)),
                        True))

    def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
        """ If an addon is already installed, set it to readonly as
        res.config.installer doesn't handle uninstallations of already
        installed addons
        """
        fields = super(res_config_installer, self).fields_get(
            cr, uid, fields, context, write_access)

        for module in self._already_installed(cr, uid, context=context):
            if module.name not in fields:
                continue
            fields[module.name].update(
                readonly=True,
                help= ustr(fields[module.name].get('help', '')) +
                     _('\n\nThis addon is already installed on your system'))
        return fields

    def execute(self, cr, uid, ids, context=None):
        modules = self.pool.get('ir.module.module')
        to_install = list(self.modules_to_install(
            cr, uid, ids, context=context))
        self.__logger.info('Selecting addons %s to install', to_install)
        modules.state_update(
            cr, uid,
            modules.search(cr, uid, [('name','in',to_install)]),
            'to install', ['uninstalled'], context=context)
        cr.commit() #TOFIX: after remove this statement, installation wizard is fail
        new_db, self.pool = pooler.restart_pool(cr.dbname, update_module=True)
res_config_installer()

DEPRECATION_MESSAGE = 'You are using an addon using old-style configuration '\
    'wizards (ir.actions.configuration.wizard). Old-style configuration '\
    'wizards have been deprecated.\n'\
    'The addon should be migrated to res.config objects.'
class ir_actions_configuration_wizard(osv.osv_memory):
    ''' Compatibility configuration wizard

    The old configuration wizard has been replaced by res.config, but in order
    not to break existing but not-yet-migrated addons, the old wizard was
    reintegrated and gutted.
    '''
    _name='ir.actions.configuration.wizard'
    _inherit = 'res.config'
    __logger = logging.getLogger(_name)

    def _next_action_note(self, cr, uid, ids, context=None):
        next = self._next_action(cr, uid)
        if next:
            # if the next one is also an old-style extension, you never know...
            if next.note:
                return next.note
            return _("Click 'Continue' to configure the next addon...")
        return _("Your database is now fully configured.\n\n"\
            "Click 'Continue' and enjoy your OpenERP experience...")

    _columns = {
        'note': fields.text('Next Wizard', readonly=True),
        }
    _defaults = {
        'note': _next_action_note,
        }

    def execute(self, cr, uid, ids, context=None):
        self.__logger.warn(DEPRECATION_MESSAGE)

ir_actions_configuration_wizard()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<commit_msg>[IMP] res.config.installer: method returning just the names of the already-installed modules in the current installer.<commit_after># -*- coding: utf-8 -*-
##############################################################################
#
#    OpenERP, Open Source Management Solution
#    Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import logging
from operator import attrgetter, itemgetter

from osv import osv, fields
from tools.translate import _
import netsvc
from tools import ustr
import pooler

class res_config_configurable(osv.osv_memory):
    ''' Base classes for new-style configuration items

    Configuration items should inherit from this class, implement
    the execute method (and optionally the cancel one) and have
    their view inherit from the related res_config_view_base view.
    '''
    _name = 'res.config'
    _inherit = 'ir.wizard.screen'
    __logger = logging.getLogger(_name)

    def get_current_progress(self, cr, uid, context=None):
        '''Return a description the current progress of configuration:
        a tuple of (non_open_todos:int, total_todos: int)
        '''
        return itemgetter('done', 'total')(
            self.pool.get('ir.actions.todo').progress(cr, uid, context=context))

    def _progress(self, cr, uid, context=None):
        closed, total = self.get_current_progress(cr, uid, context=context)
        if total:
            return round(closed*100./total)
        return 100.

    _columns = dict(
        progress = fields.float('Configuration Progress', readonly=True),
    )

    _defaults = dict(
        progress = _progress,
    )

    def _next_action(self, cr, uid, context=None):
        Todos = self.pool['ir.actions.todo']
        self.__logger.info('getting next %s', Todos)

        active_todos = Todos.browse(cr, uid,
            Todos.search(cr, uid, ['&', ('type', '=', 'special'), ('state','=','open')]),
                                    context=context)

        user_groups = set(map(
            lambda g: g.id,
            self.pool['res.users'].browse(cr, uid, [uid], context=context)[0].groups_id))

        valid_todos_for_user = [
            todo for todo in active_todos
            if not todo.groups_id or bool(user_groups.intersection((
                group.id for group in todo.groups_id)))
        ]

        if valid_todos_for_user:
            return valid_todos_for_user[0]

        return None

    def _next(self, cr, uid, context=None):
        self.__logger.info('getting next operation')
        next = self._next_action(cr, uid, context=context)
        self.__logger.info('next action is %s', next)
        if next:
            res = next.action_launch(context=context)
            res['nodestroy'] = False
            return res
        self.__logger.info('all configuration actions have been executed')

        current_user_menu = self.pool.get('res.users').browse(cr, uid, uid).menu_id
        # return the action associated with the menu
        return self.pool.get(current_user_menu.type).read(cr, uid, current_user_menu.id)

    def start(self, cr, uid, ids, context=None):
        todo_pool = self.pool.get('ir.actions.todo')
        ids2 = todo_pool.search(cr, uid, [], context=context)
        for todo in todo_pool.browse(cr, uid, ids2, context=context):
            if todo.type == 'recurring':
                todo.write({'state':'open'})
        return self.next(cr, uid, ids, context)

    def next(self, cr, uid, ids, context=None):
        """ Returns the next todo action to execute (using the default
        sort order)
        """
        return self._next(cr, uid, context=context)

    def execute(self, cr, uid, ids, context=None):
        """ Method called when the user clicks on the ``Next`` button.

        Execute *must* be overloaded unless ``action_next`` is overloaded
        (which is something you generally don't need to do).

        If ``execute`` returns an action dictionary, that action is executed
        rather than just going to the next configuration item.
        """
        raise NotImplementedError(
            'Configuration items need to implement execute')
    def cancel(self, cr, uid, ids, context=None):
        """ Method called when the user click on the ``Skip`` button.

        ``cancel`` should be overloaded instead of ``action_skip``. As with
        ``execute``, if it returns an action dictionary that action is
        executed in stead of the default (going to the next configuration item)

        The default implementation is a NOOP.

        ``cancel`` is also called by the default implementation of
        ``action_cancel``.
        """
        pass

    def action_next(self, cr, uid, ids, context=None):
        """ Action handler for the ``next`` event.

        Sets the status of the todo the event was sent from to
        ``done``, calls ``execute`` and -- unless ``execute`` returned
        an action dictionary -- executes the action provided by calling
        ``next``.
        """
        next = self.execute(cr, uid, ids, context=context)
        if next: return next
        return self.next(cr, uid, ids, context=context)

    def action_skip(self, cr, uid, ids, context=None):
        """ Action handler for the ``skip`` event.

        Sets the status of the todo the event was sent from to
        ``skip``, calls ``cancel`` and -- unless ``cancel`` returned
        an action dictionary -- executes the action provided by calling
        ``next``.
        """
        next = self.cancel(cr, uid, ids, context=context)
        if next: return next
        return self.next(cr, uid, ids, context=context)

    def action_cancel(self, cr, uid, ids, context=None):
        """ Action handler for the ``cancel`` event. That event isn't
        generated by the res.config.view.base inheritable view, the
        inherited view has to overload one of the buttons (or add one
        more).

        Sets the status of the todo the event was sent from to
        ``cancel``, calls ``cancel`` and -- unless ``cancel`` returned
        an action dictionary -- executes the action provided by calling
        ``next``.
        """
        next = self.cancel(cr, uid, ids, context=context)
        if next: return next
        return self.next(cr, uid, ids, context=context)

res_config_configurable()

class res_config_installer(osv.osv_memory):
    """ New-style configuration base specialized for addons selection
    and installation.

    Basic usage
    -----------

    Subclasses can simply define a number of _columns as
    fields.boolean objects. The keys (column names) should be the
    names of the addons to install (when selected). Upon action
    execution, selected boolean fields (and those only) will be
    interpreted as addons to install, and batch-installed.

    Additional addons
    -----------------

    It is also possible to require the installation of an additional
    addon set when a specific preset of addons has been marked for
    installation (in the basic usage only, additionals can't depend on
    one another).

    These additionals are defined through the ``_install_if``
    property. This property is a mapping of a collection of addons (by
    name) to a collection of addons (by name) [#]_, and if all the *key*
    addons are selected for installation, then the *value* ones will
    be selected as well. For example::

        _install_if = {
            ('sale','crm'): ['sale_crm'],
        }

    This will install the ``sale_crm`` addon if and only if both the
    ``sale`` and ``crm`` addons are selected for installation.

    You can define as many additionals as you wish, and additionals
    can overlap in key and value. For instance::

        _install_if = {
            ('sale','crm'): ['sale_crm'],
            ('sale','project'): ['project_mrp'],
        }

    will install both ``sale_crm`` and ``project_mrp`` if all of
    ``sale``, ``crm`` and ``project`` are selected for installation.

    Hook methods
    ------------

    Subclasses might also need to express dependencies more complex
    than that provided by additionals. In this case, it's possible to
    define methods of the form ``_if_%(name)s`` where ``name`` is the
    name of a boolean field. If the field is selected, then the
    corresponding module will be marked for installation *and* the
    hook method will be executed.

    Hook methods take the usual set of parameters (cr, uid, ids,
    context) and can return a collection of additional addons to
    install (if they return anything, otherwise they should not return
    anything, though returning any "falsy" value such as None or an
    empty collection will have the same effect).

    Complete control
    ----------------

    The last hook is to simply overload the ``modules_to_install``
    method, which implements all the mechanisms above. This method
    takes the usual set of parameters (cr, uid, ids, context) and
    returns a ``set`` of addons to install (addons selected by the
    above methods minus addons from the *basic* set which are already
    installed) [#]_ so an overloader can simply manipulate the ``set``
    returned by ``res_config_installer.modules_to_install`` to add or
    remove addons.

    Skipping the installer
    ----------------------

    Unless it is removed from the view, installers have a *skip*
    button which invokes ``action_skip`` (and the ``cancel`` hook from
    ``res.config``). Hooks and additionals *are not run* when skipping
    installation, even for already installed addons.

    Again, setup your hooks accordingly.

    .. [#] note that since a mapping key needs to be hashable, it's
           possible to use a tuple or a frozenset, but not a list or a
           regular set

    .. [#] because the already-installed modules are only pruned at
           the very end of ``modules_to_install``, additionals and
           hooks depending on them *are guaranteed to execute*. Setup
           your hooks accordingly.
    """
    _name = 'res.config.installer'
    _inherit = 'res.config'
    __logger = logging.getLogger(_name)

    _install_if = {}

    def already_installed(self, cr, uid, context=None):
        """ For each module, check if it's already installed and if it
        is return its name

        :returns: a list of the already installed modules in this
                  installer
        :rtype: [str]
        """
        return map(attrgetter('name'),
                   self._already_installed(cr, uid, context=context))

    def _already_installed(self, cr, uid, context=None):
        """ For each module (boolean fields in a res.config.installer),
        check if it's already installed (either 'to install', 'to upgrade'
        or 'installed') and if it is return the module's browse_record

        :returns: a list of all installed modules in this installer
        :rtype: [browse_record]
        """
        modules = self.pool.get('ir.module.module')

        selectable = [field for field in self._columns
                      if type(self._columns[field]) is fields.boolean]
        return modules.browse(
            cr, uid,
            modules.search(cr, uid,
                           [('name','in',selectable),
                            ('state','in',['to install', 'installed', 'to upgrade'])],
                           context=context),
            context=context)

    def modules_to_install(self, cr, uid, ids, context=None):
        """ selects all modules to install:

        * checked boolean fields
        * return values of hook methods. Hook methods are of the form
          ``_if_%(addon_name)s``, and are called if the corresponding
          addon is marked for installation. They take the arguments
          cr, uid, ids and context, and return an iterable of addon
          names
        * additionals, additionals are setup through the ``_install_if``
          class variable. ``_install_if`` is a dict of {iterable:iterable}
          where key and value are iterables of addon names.

          If all the addons in the key are selected for installation
          (warning: addons added through hooks don't count), then the
          addons in the value are added to the set of modules to install
        * not already installed
        """
        base = set(module_name
                   for installer in self.read(cr, uid, ids, context=context)
                   for module_name, to_install in installer.iteritems()
                   if module_name != 'id'
                   if type(self._columns[module_name]) is fields.boolean
                   if to_install)

        hooks_results = set()
        for module in base:
            hook = getattr(self, '_if_%s'%(module), None)
            if hook:
                hooks_results.update(hook(cr, uid, ids, context=None) or set())

        additionals = set(
            module for requirements, consequences \
                       in self._install_if.iteritems()
                   if base.issuperset(requirements)
                   for module in consequences)

        return (base | hooks_results | additionals).difference(
                    self.already_installed(cr, uid, context))

    def default_get(self, cr, uid, fields_list, context=None):
        ''' If an addon is already installed, check it by default
        '''
        defaults = super(res_config_installer, self).default_get(
            cr, uid, fields_list, context=context)

        return dict(defaults,
                    **dict.fromkeys(
                        self.already_installed(cr, uid, context=context),
                        True))

    def fields_get(self, cr, uid, fields=None, context=None, write_access=True):
        """ If an addon is already installed, set it to readonly as
        res.config.installer doesn't handle uninstallations of already
        installed addons
        """
        fields = super(res_config_installer, self).fields_get(
            cr, uid, fields, context, write_access)

        for name in self.already_installed(cr, uid, context=context):
            if name not in fields:
                continue
            fields[name].update(
                readonly=True,
                help= ustr(fields[name].get('help', '')) +
                     _('\n\nThis addon is already installed on your system'))
        return fields

    def execute(self, cr, uid, ids, context=None):
        modules = self.pool.get('ir.module.module')
        to_install = list(self.modules_to_install(
            cr, uid, ids, context=context))
        self.__logger.info('Selecting addons %s to install', to_install)
        modules.state_update(
            cr, uid,
            modules.search(cr, uid, [('name','in',to_install)]),
            'to install', ['uninstalled'], context=context)
        cr.commit() #TOFIX: after remove this statement, installation wizard is fail
        new_db, self.pool = pooler.restart_pool(cr.dbname, update_module=True)
res_config_installer()

DEPRECATION_MESSAGE = 'You are using an addon using old-style configuration '\
    'wizards (ir.actions.configuration.wizard). Old-style configuration '\
    'wizards have been deprecated.\n'\
    'The addon should be migrated to res.config objects.'
class ir_actions_configuration_wizard(osv.osv_memory):
    ''' Compatibility configuration wizard

    The old configuration wizard has been replaced by res.config, but in order
    not to break existing but not-yet-migrated addons, the old wizard was
    reintegrated and gutted.
    '''
    _name='ir.actions.configuration.wizard'
    _inherit = 'res.config'
    __logger = logging.getLogger(_name)

    def _next_action_note(self, cr, uid, ids, context=None):
        next = self._next_action(cr, uid)
        if next:
            # if the next one is also an old-style extension, you never know...
            if next.note:
                return next.note
            return _("Click 'Continue' to configure the next addon...")
        return _("Your database is now fully configured.\n\n"\
            "Click 'Continue' and enjoy your OpenERP experience...")

    _columns = {
        'note': fields.text('Next Wizard', readonly=True),
        }
    _defaults = {
        'note': _next_action_note,
        }

    def execute(self, cr, uid, ids, context=None):
        self.__logger.warn(DEPRECATION_MESSAGE)

ir_actions_configuration_wizard()

# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
<|endoftext|>


<commit_before>"""Support for reading DWI data from DICOM files."""

from __future__ import absolute_import, division, print_function
import os.path

import numpy as np
import dicom

import dwi.util

def read_dir(dirname):
    """Read a directory containing DICOM files. See dicomfile.read_files().
    """
    # Sometimes the files reside in an additional 'DICOM' subdirectory.
    path = os.path.join(dirname, 'DICOM')
    if os.path.isdir(path):
        dirname = path
    filenames = os.listdir(dirname)
    pathnames = [os.path.join(dirname, f) for f in filenames]
    return read_files(pathnames)

def read_files(filenames):
    """Read a bunch of files, each containing a single slice with one b-value,
    and construct a 4d image array.

    The slices are sorted simply by their position as it is, assuming it only
    changes in one dimension. In case there are more than one scan of
    a position and a b-value, the files are averaged by mean.

    DICOM files without pixel data are silently skipped.
    """
    d = {}
    for f in filenames:
        read_slice(f, d)
    positions = sorted(d['positions'])
    bvalues = sorted(d['bvalues'])
    echotimes = sorted(d['echotimes'])
    slices = d['slices']
    # If any slices are scanned multiple times, use mean.
    for k, v in slices.iteritems():
        slices[k] = np.mean(v, axis=0)
    image = construct_image(slices, positions, bvalues, echotimes)
    if len(bvalues) == image.shape[-1]:
        parameters = list(bvalues)
    elif len(echotimes) == image.shape[-1]:
        parameters = list(echotimes)
    else:
        raise ValueError('Inconsistent parameters')
    r = dict(image=image, bset=bvalues, echotimes=echotimes,
             parameters=parameters, voxel_spacing=d['voxel_spacing'])
    return r

def read_slice(filename, d):
    """Read a single slice."""
    try:
        df = dicom.read_file(filename)
    except dicom.filereader.InvalidDicomError as e:
        dwi.util.report('Error reading {f}: {e}'.format(f=filename, e=e))
        return
    if 'PixelData' not in df:
        return
    d.setdefault('orientation', df.ImageOrientationPatient)
    if d['orientation'] != df.ImageOrientationPatient:
        raise Exception('Orientation mismatch.')
    d.setdefault('shape', df.pixel_array.shape)
    if d['shape'] != df.pixel_array.shape:
        raise Exception('Shape mismatch.')
    d.setdefault('dtype', df.pixel_array.dtype)
    if d['dtype'] != df.pixel_array.dtype:
        raise Exception('Type mismatch.')
    d.setdefault('voxel_spacing', get_voxel_spacing(df))
    position = tuple(float(x) for x in df.ImagePositionPatient)
    bvalue = get_bvalue(df)
    echotime = df.get('EchoTime')
    pixels = get_pixels(df)
    d.setdefault('positions', set()).add(position)
    d.setdefault('bvalues', set()).add(bvalue)
    d.setdefault('echotimes', set()).add(echotime)
    key = (position, bvalue, echotime)
    slices = d.setdefault('slices', {})  # Indexed by (pos, b, echotime)...
    slices.setdefault(key, []).append(pixels)  # ...are lists of slices.

def construct_image(slices, positions, bvalues, echotimes):
    """Construct uniform image array from slice dictionary."""
    w, h = slices.values()[0].shape
    dtype = slices.values()[0].dtype
    shape = (len(positions), w, h, len(bvalues), len(echotimes))
    image = np.empty(shape, dtype=dtype)
    image.fill(np.nan)
    for key, value in slices.iteritems():
        pos = positions.index(key[0])
        bv = bvalues.index(key[1])
        et = echotimes.index(key[2])
        image[pos, :, :, bv, et] = value
    if image.shape[3] == 1:
        image = image.squeeze(axis=3)
    elif image.shape[4] == 1:
        image = image.squeeze(axis=4)
    assert image.ndim == 4, image.shape
    if np.isnan(np.min(image)):
        raise Exception('Slices missing from shape {:s}.'.format(shape))
    return image

def get_bvalue(df):
    """Return image b-value. It may also be stored as frame second."""
    if 'DiffusionBValue' in df:
        r = df.DiffusionBValue
    elif 'FrameTime' in df:
        r = df.FrameTime / 1000
    elif 'FrameReferenceTime' in df:
        r = df.FrameReferenceTime / 1000
    else:
        raise AttributeError('DICOM file does not contain a b-value')
    return r

def get_pixels(df):
    """Return rescaled pixel array from DICOM object."""
    pixels = df.pixel_array
    pixels = pixels.astype(np.float64)
    pixels = pixels * df.RescaleSlope + df.RescaleIntercept
    # # Clipping should not be done.
    # lowest = df.WindowCenter - df.WindowWidth/2
    # highest = df.WindowCenter + df.WindowWidth/2
    # pixels = pixels.clip(lowest, highest, out=pixels)
    return pixels

def get_voxel_spacing(df):
    """Return voxel spacing in millimeters as (z, y, x)."""
    # Note: Some manufacturers misinterpret SpacingBetweenSlices, it would be
    # better to calculate this from ImageOrientationPatient and
    # ImagePositionPatient.
    z = df.SpacingBetweenSlices if 'SpacingBetweenSlices' in df else 1.
    x, y = df.PixelSpacing if 'PixelSpacing' in df else (1., 1.)
    return tuple(float(n) for n in (z, y, x))
<commit_msg>don't allow overlapping slices no moar<commit_after>"""Support for reading DWI data from DICOM files."""

from __future__ import absolute_import, division, print_function
import os.path

import numpy as np
import dicom

import dwi.util

def read_dir(dirname):
    """Read a directory containing DICOM files. See dicomfile.read_files().
    """
    # Sometimes the files reside in an additional 'DICOM' subdirectory.
    path = os.path.join(dirname, 'DICOM')
    if os.path.isdir(path):
        dirname = path
    filenames = os.listdir(dirname)
    pathnames = [os.path.join(dirname, f) for f in filenames]
    return read_files(pathnames)

def read_files(filenames):
    """Read a bunch of files, each containing a single slice with one b-value,
    and construct a 4d image array.

    The slices are sorted simply by their position as it is, assuming it only
    changes in one dimension. In case there are more than one scan of
    a position and a b-value, the files are averaged by mean.

    DICOM files without pixel data are silently skipped.
    """
    d = {}
    for f in filenames:
        read_slice(f, d)
    positions = sorted(d['positions'])
    bvalues = sorted(d['bvalues'])
    echotimes = sorted(d['echotimes'])
    image = construct_image(d['slices'], positions, bvalues, echotimes)
    if len(bvalues) == image.shape[-1]:
        parameters = list(bvalues)
    elif len(echotimes) == image.shape[-1]:
        parameters = list(echotimes)
    else:
        raise ValueError('Inconsistent parameters')
    r = dict(image=image, bset=bvalues, echotimes=echotimes,
             parameters=parameters, voxel_spacing=d['voxel_spacing'])
    return r

def read_slice(filename, d):
    """Read a single slice."""
    try:
        df = dicom.read_file(filename)
    except dicom.filereader.InvalidDicomError as e:
        dwi.util.report('Error reading {f}: {e}'.format(f=filename, e=e))
        return
    if 'PixelData' not in df:
        return
    d.setdefault('orientation', df.ImageOrientationPatient)
    if d['orientation'] != df.ImageOrientationPatient:
        raise Exception('Orientation mismatch.')
    d.setdefault('shape', df.pixel_array.shape)
    if d['shape'] != df.pixel_array.shape:
        raise Exception('Shape mismatch.')
    d.setdefault('dtype', df.pixel_array.dtype)
    if d['dtype'] != df.pixel_array.dtype:
        raise Exception('Type mismatch.')
    d.setdefault('voxel_spacing', get_voxel_spacing(df))
    position = tuple(float(x) for x in df.ImagePositionPatient)
    bvalue = get_bvalue(df)
    echotime = df.get('EchoTime')
    pixels = get_pixels(df)
    d.setdefault('positions', set()).add(position)
    d.setdefault('bvalues', set()).add(bvalue)
    d.setdefault('echotimes', set()).add(echotime)
    key = (position, bvalue, echotime)
    slices = d.setdefault('slices', {})
    if key in slices:
        raise ValueError('Overlapping slices: {}, {}'.format(key, filename))
    slices[key] = pixels

def construct_image(slices, positions, bvalues, echotimes):
    """Construct uniform image array from slice dictionary."""
    w, h = slices.values()[0].shape
    dtype = slices.values()[0].dtype
    shape = (len(positions), w, h, len(bvalues), len(echotimes))
    image = np.empty(shape, dtype=dtype)
    image.fill(np.nan)
    for key, value in slices.iteritems():
        pos = positions.index(key[0])
        bv = bvalues.index(key[1])
        et = echotimes.index(key[2])
        image[pos, :, :, bv, et] = value
    if image.shape[3] == 1:
        image = image.squeeze(axis=3)
    elif image.shape[4] == 1:
        image = image.squeeze(axis=4)
    assert image.ndim == 4, image.shape
    if np.isnan(np.min(image)):
        raise Exception('Slices missing from shape {:s}.'.format(shape))
    return image

def get_bvalue(df):
    """Return image b-value. It may also be stored as frame second."""
    if 'DiffusionBValue' in df:
        r = df.DiffusionBValue
    elif 'FrameTime' in df:
        r = df.FrameTime / 1000
    elif 'FrameReferenceTime' in df:
        r = df.FrameReferenceTime / 1000
    else:
        raise AttributeError('DICOM file does not contain a b-value')
    return r

def get_pixels(df):
    """Return rescaled pixel array from DICOM object."""
    pixels = df.pixel_array
    pixels = pixels.astype(np.float64)
    pixels = pixels * df.RescaleSlope + df.RescaleIntercept
    # # Clipping should not be done.
    # lowest = df.WindowCenter - df.WindowWidth/2
    # highest = df.WindowCenter + df.WindowWidth/2
    # pixels = pixels.clip(lowest, highest, out=pixels)
    return pixels

def get_voxel_spacing(df):
    """Return voxel spacing in millimeters as (z, y, x)."""
    # Note: Some manufacturers misinterpret SpacingBetweenSlices, it would be
    # better to calculate this from ImageOrientationPatient and
    # ImagePositionPatient.
    z = df.SpacingBetweenSlices if 'SpacingBetweenSlices' in df else 1.
    x, y = df.PixelSpacing if 'PixelSpacing' in df else (1., 1.)
    return tuple(float(n) for n in (z, y, x))
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-

# OpenFisca -- A versatile microsimulation software
# By: OpenFisca Team <contact@openfisca.fr>
#
# Copyright (C) 2011, 2012, 2013, 2014, 2015 OpenFisca Team
# https://github.com/openfisca
#
# This file is part of OpenFisca.
#
# OpenFisca is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# OpenFisca is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from webob import Request
from unittest.case import SkipTest

from . import common

def setup_module(module):
    common.get_or_load_app()

def test_graph_without_parameters():
    raise SkipTest('NoneType is not iterable. I do not know how to fix this.')

    req = Request.blank('/api/1/graph', method = 'GET')
    res = req.get_response(common.app)
    assert res.status_code == 200
<commit_msg>Do not skip graph test anymore<commit_after># -*- coding: utf-8 -*-

# OpenFisca -- A versatile microsimulation software
# By: OpenFisca Team <contact@openfisca.fr>
#
# Copyright (C) 2011, 2012, 2013, 2014, 2015 OpenFisca Team
# https://github.com/openfisca
#
# This file is part of OpenFisca.
#
# OpenFisca is free software; you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# OpenFisca is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from webob import Request

from . import common

def setup_module(module):
    common.get_or_load_app()

def test_graph_without_parameters():
    req = Request.blank('/api/1/graph', method = 'GET')
    res = req.get_response(common.app)
    assert res.status_code == 200
<|endoftext|>


<commit_before>from snapshot import AbstractSnapshot, FeatureSnapshot, Snapshot, MDSnapshot, ToySnapshot
from trajectory import Trajectory

from topology import Topology

import features

from dynamics_engine import DynamicsEngine<commit_msg>repair features after rebase<commit_after>from snapshot import BaseSnapshot, FeatureSnapshot, Snapshot, MDSnapshot, ToySnapshot
from trajectory import Trajectory

from topology import Topology

import features

from dynamics_engine import DynamicsEngine<|endoftext|>


<commit_before>from numpy import testing as npt
import openpathsampling as paths
import os

import openmmtools

class TestStaticContainerStore(object):
    def teardown(self):
        if os.path.isfile("test.nc"):
            os.remove("test.nc")

    def test_store_nonperiodic(self):
        testsystem = openmmtools.testsystems.AlanineDipeptideVacuum()
        snap = paths.engines.openmm.snapshot_from_testsystem(testsystem,
                                                             periodic=False)
        storage = paths.Storage("test.nc", 'w')
        storage.save(snap)
        storage.close()
        load = paths.Storage("test.nc", 'r')
        reloaded = load.snapshots[0]
        npt.assert_array_equal(snap.coordinates, reloaded.coordinates)
        npt.assert_array_equal(snap.box_vectors, reloaded.box_vectors)
<commit_msg>add explicit tests that the box vectors are None<commit_after>from numpy import testing as npt
import openpathsampling as paths
import os

import openmmtools

class TestStaticContainerStore(object):
    def teardown(self):
        if os.path.isfile("test.nc"):
            os.remove("test.nc")

    def test_store_nonperiodic(self):
        testsystem = openmmtools.testsystems.AlanineDipeptideVacuum()
        snap = paths.engines.openmm.snapshot_from_testsystem(testsystem,
                                                             periodic=False)
        storage = paths.Storage("test.nc", 'w')
        storage.save(snap)
        storage.close()
        load = paths.Storage("test.nc", 'r')
        reloaded = load.snapshots[0]
        npt.assert_array_equal(snap.coordinates, reloaded.coordinates)
        npt.assert_array_equal(snap.box_vectors, reloaded.box_vectors)
        assert snap.box_vectors is None
        assert reloaded.box_vectors is None
<|endoftext|>


<commit_before>"""functions to create plots, basis for figures in article"""
import matplotlib.pyplot as plt
import seaborn as sns

plt.style.use('ggplot')

def frame_error_test_mn_plot(df, save_as=None):
    ax = sns.stripplot(x="train_set_dur", y="frame_error_test_mean", data=df, size=12, alpha=0.75)
    sns.boxplot(x="train_set_dur", y="frame_error_test_mean", data=df, ax=ax, showfliers=False)
#    ax.set_ylabel("frame error, test set")
#    ax.set_xlabel("training set duration (s)")

    ax.set_title('Frame error rate as a function of training set size', fontsize=40)
    ax.set_ylabel('Frame error rate\nas measured on test set', fontsize=32)
    ax.set_xlabel('Training set size: duration in s', fontsize=32)

    if save_as:
        plt.savefig(save_as)

    return ax

def syllable_error_rate(all_results_list):
    plt.style.use('ggplot')
    fig, ax = plt.subplots()
    fig.set_size_inches(16, 8)
    for el in all_results_list:
        lbl = (el['bird_ID'])
        ax.plot(el['train_set_durs'],
                el['mn_test_syl_err'],
                label=lbl,
                linestyle=':',
                marker='o')

    plt.scatter(120, 0.84, s=75)
    plt.text(75, 0.7, 'Koumura & Okanoya 2016,\n0.84 note error rate\nwith 120s training data', fontsize=20)
    plt.scatter(480, 0.5, s=75)
    plt.text(355, 0.35, 'Koumura & Okanoya 2016,\n0.46 note error rate\nwith 480s training data', fontsize=20)

    plt.legend(fontsize=20, loc='upper right');
    plt.title('Syllable error rate as a function of training set size', fontsize=40)
    plt.xticks(el['train_set_durs'])
    plt.tick_params(axis='both', which='major', labelsize=20, rotation=45)
    plt.ylabel('Syllable error rate\nas measured on test set', fontsize=32)
    plt.xlabel('Training set size: duration in s', fontsize=32);
    plt.tight_layout()
    plt.savefig('syl-error-rate-v-train-set-size.png')<commit_msg>update plots in plot.py -- versions used for talk<commit_after>"""functions to create plots, basis for figures in article"""
import matplotlib.pyplot as plt
import seaborn as sns

def frame_error_rate_test_mean(df, ax=None, figsize=(10, 7.5), save_as=None):
    if ax is None:
        fig, ax = plt.subplots()
        fig.set_size_inches(figsize)
    sns.pointplot(x="train_set_dur", y="frame_error_test_mean",
                  hue="animal_ID", data=df,
                  linestyles=['--' for _ in df.animal_ID.unique()],
                  ax=ax)

    mean_fet_mn = df.groupby('train_set_dur')['frame_error_test_mean'].mean()
    with plt.rc_context({'lines.linewidth': 2}):
        mn_plot = sns.pointplot(mean_fet_mn.index,
                                mean_fet_mn.values,
                                color='black', ax=ax)

    ax.set_ylabel("frame error,\ntest set", fontsize=28)
    ax.set_xlabel("training set duration (s)", fontsize=28)
    ax.tick_params(labelsize=20)
    ax.legend(bbox_to_anchor=(1.01, 1), loc=2,
              borderaxespad=0., fontsize=20)

    max_zorder = max([_.zorder for _ in ax.get_children()])
    mn_plot.set_zorder(max_zorder + 1)

    if save_as:
        plt.savefig(save_as)

    return ax

def syllable_error_rate_test_mean(df, ax=None, figsize=(10, 7.5), save_as=None):
    if ax is None:
        fig, ax = plt.subplots()
        fig.set_size_inches(figsize)

    sns.pointplot(x="train_set_dur", y="syllable_error_test_mean",
                hue="animal_ID", data=df,
                linestyles=['--' for _ in df.animal_ID.unique()],
                ax=ax)

    mean_sert_mn = df.groupby('train_set_dur')['syllable_error_test_mean'].mean()
    with plt.rc_context({'lines.linewidth': 2}):
        mn_plot = sns.pointplot(mean_sert_mn.index,
                                mean_sert_mn.values,
                                color='black', ax=ax)

    ax.set_ylabel("syllable error rate,\ntest set", fontsize=28)
    ax.set_xlabel("training set duration (s)", fontsize=28)
    ax.tick_params(labelsize=20)
    ax.legend(bbox_to_anchor=(1.01, 1), loc=2,
              borderaxespad=0., fontsize=20)

    max_zorder = max([_.zorder for _ in ax.get_children()])
    mn_plot.set_zorder(max_zorder + 1)

    if save_as:
        plt.savefig(save_as)

    return ax
<|endoftext|>


<commit_before>try:
    import urllib3.contrib.pyopenssl
    urllib3.contrib.pyopenssl.inject_into_urllib3()
except ImportError:
    pass

import argparse
import logging
import logging.config
import os
from time import sleep, mktime, time
from urlparse import urljoin

from datetime import datetime
from dateutil.tz import tzlocal
from subprocess import check_output
from couchdb import Database, Session
from time import time
import iso8601
from .design import endDate_view, startDate_view
from .utils import do_until_success, generate_request_id
from yaml import load
from systemd_msgs_ids import (
    DATA_BRIDGE_RE_PLANNING,
    DATA_BRIDGE_PLANNING,
    DATA_BRIDGE_PLANNING_PROCESS
)

from openprocurement_client.client import Client as ApiClient

SIMPLE_AUCTION_TYPE = 0
SINGLE_LOT_AUCTION_TYPE = 1

MULTILOT_AUCTION_ID = "{0[id]}_{1[id]}" # {TENDER_ID}_{LOT_ID}

logger = logging.getLogger(__name__)

class AuctionsDataBridge(object):

    """AuctionsDataBridge"""

    def __init__(self, config):
        super(AuctionsDataBridge, self).__init__()
        self.config = config
        self.client = ApiClient(
            '',
            host_url=self.config_get('tenders_api_server'),
            api_version=self.config_get('tenders_api_version')
        )
        self.tz = tzlocal()
        self.couch_url = urljoin(
            self.config_get('couch_url'),
            self.config_get('auctions_db')
        )
        self.db = Database(self.couch_url,
                           session=Session(retry_delays=range(10)))

    def config_get(self, name):
        return self.config.get('main').get(name)

    def tender_url(self, tender_id):
        return urljoin(self.tenders_url, 'tenders/{}/auction'.format(tender_id))

    def get_teders_list(self, re_planning=False):
        while True:
            request_id = generate_request_id(prefix=b'data-bridge-req-')
            self.client.headers.update({'X-Client-Request-ID': request_id})
            tenders_list = list(self.client.get_tenders())
            if tenders_list:
                logger.info("Client params: {}".format(self.client.params))
                for item in tenders_list:
                    if item['status'] == "active.auction":
                        if 'auctionPeriod' in item and 'startDate' in item['auctionPeriod'] \
                                and 'endDate' not in item['auctionPeriod']:

                            start_date = iso8601.parse_date(item['auctionPeriod']['startDate'])
                            start_date = start_date.astimezone(self.tz)
                            auctions_start_in_date = startDate_view(
                                self.db,
                                key=(mktime(start_date.timetuple()) + start_date.microsecond / 1E6) * 1000
                            )
                            if datetime.now(self.tz) > start_date:
                                logger.info("Tender {} start date in past. Skip it for planning".format(item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                continue
                            if re_planning and item['id'] in self.tenders_ids_list:
                                logger.info("Tender {} already planned while replanning".format(item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                continue
                            elif not re_planning and [row.id for row in auctions_start_in_date.rows if row.id == item['id']]:
                                logger.info("Tender {} already planned on same date".format(item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                continue
                            yield (str(item['id']), )
                        elif 'lots' in item:
                            for lot in item['lots']:
                                if lot["status"] == "active" and 'startDate' in lot['auctionPeriod'] \
                                        and 'endDate' not in lot['auctionPeriod']:
                                    start_date = iso8601.parse_date(lot['auctionPeriod']['startDate'])
                                    start_date = start_date.astimezone(self.tz)
                                    auctions_start_in_date = startDate_view(
                                        self.db,
                                        key=(mktime(start_date.timetuple()) + start_date.microsecond / 1E6) * 1000
                                    )
                                    if datetime.now(self.tz) > start_date:
                                        logger.info(
                                            "Start date for lot {} in tender {} is in past. Skip it for planning".format(
                                                lot['id'], item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING}
                                        )
                                        continue
                                    auction_id = MULTILOT_AUCTION_ID.format(item, lot)
                                    if re_planning and auction_id in self.tenders_ids_list:
                                        logger.info("Tender {} already planned while replanning".format(auction_id),
                                                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                        continue
                                    elif not re_planning and [row.id for row in auctions_start_in_date.rows if row.id == auction_id]:
                                        logger.info("Tender {} already planned on same date".format(auction_id),
                                                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                        continue
                                    yield (str(item["id"]), str(lot["id"]), )

                    if item['status'] == "cancelled":
                        future_auctions = endDate_view(
                            self.db, startkey=time() * 1000
                        )
                        if item["id"] in [i.id for i in future_auctions]:
                            logger.info("Tender {} canceled".format(item["id"]),
                                        extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                            auction_document = self.db[item["id"]]
                            auction_document["current_stage"] = -100
                            auction_document["endDate"] = datetime.now(self.tz).isoformat()
                            self.db.save(auction_document)
                            logger.info("Change auction {} status to 'canceled'".format(item["id"]),
                                        extra={"JOURNAL_REQUEST_ID": request_id,
                                               'MESSAGE_ID': DATA_BRIDGE_PLANNING})

            else:
                break

    def start_auction_worker(self, tender_id, with_api_version=None, lot_id=None):
        params = [self.config_get('auction_worker'),
                  'planning', tender_id,
                  self.config_get('auction_worker_config')]
        if lot_id:
            params += ['--lot', lot_id]

        if with_api_version:
            params += ['--with_api_version', with_api_version]
        result = do_until_success(
            check_output,
            args=(params,),
        )

        logger.info("Auction planning command result: {}".format(repr(result)),
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING_PROCESS})

    def planning_with_couch(self):
        logger.info('Start Auctions Bridge with feed to couchdb',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        logger.info('Start data sync...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        self.planned_tenders = {}
        self.last_seq_id = 0
        while True:
            do_until_success(self.handle_continuous_feed)

    def handle_continuous_feed(self):
        change = self.db.changes(feed='continuous', filter="auctions/by_startDate",
                                 since=self.last_seq_id, include_docs=True)
        for auction_item in change:
            if 'id' in auction_item:
                start_date = auction_item['doc']['stages'][0]['start']
                if auction_item['doc'].get("current_stage", "") == -100:
                    continue

                if auction_item['doc'].get("mode", "") == "test":
                    logger.info('Sciped test auction {}'.format(auction_item['id']),
                                extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                    continue

                if auction_item['id'] in self.planned_tenders and \
                        self.planned_tenders[auction_item['id']] == start_date:
                    logger.debug('Tender {} filtered'.format(auction_item['id']))
                    continue
                logger.info('Tender {} selected for planning'.format(auction_item['id']),
                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})

                if "_" in auction_item['id']:
                    tender_id, lot_id = auction_item['id'].split("_")
                else:
                    tender_id = auction_item['id']
                    lot_id = None

                self.start_auction_worker(
                    tender_id, lot_id=lot_id,
                    with_api_version=auction_item['doc'].get('TENDERS_API_VERSION', None)
                )

                self.planned_tenders[auction_item['id']] = start_date
            elif 'last_seq' in auction_item:
                self.last_seq_id = auction_item['last_seq']

        logger.info('Resume data sync...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})

    def run(self):
        logger.info('Start Auctions Bridge',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        self.client.params.update({'opt_fields': 'status,auctionPeriod,lots', 'mode': '_all_'})

        logger.info('Start data sync...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        while True:
            for planning_data in self.get_teders_list():
                # TODO: yield (SIMPLE_AUCTION_TYPE, item)
                if len(planning_data) == 1:
                    logger.info('Tender {0} selected for planning'.format(*planning_data))
                    self.start_auction_worker(planning_data[0])
                elif len(planning_data) == 2:
                    logger.info('Lot {1} of tender {0} selected for planning'.format(*planning_data))
                    self.start_auction_worker(planning_data[0], lot=planning_data[1])
            logger.info('Sleep...',
                        extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
            sleep(100)
            logger.info('Resume data sync...',
                        extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})

    def run_re_planning(self):
        self.re_planning = True
        self.tenders_ids_list = []
        # TODO: support mulilots
        self.offset = ''
        logger.info('Start Auctions Bridge for re-planning...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_RE_PLANNING})
        for tender_item in self.get_teders_list(re_planning=True):
            logger.debug('Tender {} selected for re-planning'.format(tender_item))
            self.start_auction_worker(tender_item)
            self.tenders_ids_list.append(tender_item['id'])
            sleep(1)
        logger.info("Re-planning auctions finished",
                    extra={'MESSAGE_ID': DATA_BRIDGE_RE_PLANNING})

def main():
    parser = argparse.ArgumentParser(description='---- Auctions Bridge ----')
    parser.add_argument('config', type=str, help='Path to configuration file')
    parser.add_argument(
        '--re-planning', action='store_true', default=False,
        help='Not ignore auctions which already scheduled')
    parser.add_argument(
        '--planning-with-couch', action='store_true', default=False,
        help='Use couchdb for tenders feed')
    params = parser.parse_args()
    if os.path.isfile(params.config):
        with open(params.config) as config_file_obj:
            config = load(config_file_obj.read())
        logging.config.dictConfig(config)
        if params.planning_with_couch:
            AuctionsDataBridge(config).planning_with_couch()
        elif params.re_planning:
            AuctionsDataBridge(config).run_re_planning()
        else:
            AuctionsDataBridge(config).run()

##############################################################

if __name__ == "__main__":
    main()
<commit_msg>Fixed planning<commit_after>try:
    import urllib3.contrib.pyopenssl
    urllib3.contrib.pyopenssl.inject_into_urllib3()
except ImportError:
    pass

import argparse
import logging
import logging.config
import os
from time import sleep, mktime, time
from urlparse import urljoin

from datetime import datetime
from dateutil.tz import tzlocal
from subprocess import check_output
from couchdb import Database, Session
from time import time
import iso8601
from .design import endDate_view, startDate_view
from .utils import do_until_success, generate_request_id
from yaml import load
from systemd_msgs_ids import (
    DATA_BRIDGE_RE_PLANNING,
    DATA_BRIDGE_PLANNING,
    DATA_BRIDGE_PLANNING_PROCESS
)

from openprocurement_client.client import Client as ApiClient

SIMPLE_AUCTION_TYPE = 0
SINGLE_LOT_AUCTION_TYPE = 1

MULTILOT_AUCTION_ID = "{0[id]}_{1[id]}" # {TENDER_ID}_{LOT_ID}

logger = logging.getLogger(__name__)

class AuctionsDataBridge(object):

    """AuctionsDataBridge"""

    def __init__(self, config):
        super(AuctionsDataBridge, self).__init__()
        self.config = config
        self.client = ApiClient(
            '',
            host_url=self.config_get('tenders_api_server'),
            api_version=self.config_get('tenders_api_version')
        )
        self.tz = tzlocal()
        self.couch_url = urljoin(
            self.config_get('couch_url'),
            self.config_get('auctions_db')
        )
        self.db = Database(self.couch_url,
                           session=Session(retry_delays=range(10)))

    def config_get(self, name):
        return self.config.get('main').get(name)

    def tender_url(self, tender_id):
        return urljoin(self.tenders_url, 'tenders/{}/auction'.format(tender_id))

    def get_teders_list(self, re_planning=False):
        while True:
            request_id = generate_request_id(prefix=b'data-bridge-req-')
            self.client.headers.update({'X-Client-Request-ID': request_id})
            tenders_list = list(self.client.get_tenders())
            if tenders_list:
                logger.info("Client params: {}".format(self.client.params))
                for item in tenders_list:
                    if item['status'] == "active.auction":
                        if 'auctionPeriod' in item and 'startDate' in item['auctionPeriod'] \
                                and 'endDate' not in item['auctionPeriod']:

                            start_date = iso8601.parse_date(item['auctionPeriod']['startDate'])
                            start_date = start_date.astimezone(self.tz)
                            auctions_start_in_date = startDate_view(
                                self.db,
                                key=(mktime(start_date.timetuple()) + start_date.microsecond / 1E6) * 1000
                            )
                            if datetime.now(self.tz) > start_date:
                                logger.info("Tender {} start date in past. Skip it for planning".format(item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                continue
                            if re_planning and item['id'] in self.tenders_ids_list:
                                logger.info("Tender {} already planned while replanning".format(item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                continue
                            elif not re_planning and [row.id for row in auctions_start_in_date.rows if row.id == item['id']]:
                                logger.info("Tender {} already planned on same date".format(item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                continue
                            yield (str(item['id']), )
                        elif 'lots' in item:
                            for lot in item['lots']:
                                if lot["status"] == "active" and 'startDate' in lot['auctionPeriod'] \
                                        and 'endDate' not in lot['auctionPeriod']:
                                    start_date = iso8601.parse_date(lot['auctionPeriod']['startDate'])
                                    start_date = start_date.astimezone(self.tz)
                                    auctions_start_in_date = startDate_view(
                                        self.db,
                                        key=(mktime(start_date.timetuple()) + start_date.microsecond / 1E6) * 1000
                                    )
                                    if datetime.now(self.tz) > start_date:
                                        logger.info(
                                            "Start date for lot {} in tender {} is in past. Skip it for planning".format(
                                                lot['id'], item['id']),
                                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING}
                                        )
                                        continue
                                    auction_id = MULTILOT_AUCTION_ID.format(item, lot)
                                    if re_planning and auction_id in self.tenders_ids_list:
                                        logger.info("Tender {} already planned while replanning".format(auction_id),
                                                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                        continue
                                    elif not re_planning and [row.id for row in auctions_start_in_date.rows if row.id == auction_id]:
                                        logger.info("Tender {} already planned on same date".format(auction_id),
                                                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                                        continue
                                    yield (str(item["id"]), str(lot["id"]), )

                    if item['status'] == "cancelled":
                        future_auctions = endDate_view(
                            self.db, startkey=time() * 1000
                        )
                        if item["id"] in [i.id for i in future_auctions]:
                            logger.info("Tender {} canceled".format(item["id"]),
                                        extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                            auction_document = self.db[item["id"]]
                            auction_document["current_stage"] = -100
                            auction_document["endDate"] = datetime.now(self.tz).isoformat()
                            self.db.save(auction_document)
                            logger.info("Change auction {} status to 'canceled'".format(item["id"]),
                                        extra={"JOURNAL_REQUEST_ID": request_id,
                                               'MESSAGE_ID': DATA_BRIDGE_PLANNING})

            else:
                break

    def start_auction_worker(self, tender_id, with_api_version=None, lot_id=None):
        params = [self.config_get('auction_worker'),
                  'planning', tender_id,
                  self.config_get('auction_worker_config')]
        if lot_id:
            params += ['--lot', lot_id]

        if with_api_version:
            params += ['--with_api_version', with_api_version]
        result = do_until_success(
            check_output,
            args=(params,),
        )

        logger.info("Auction planning command result: {}".format(repr(result)),
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING_PROCESS})

    def planning_with_couch(self):
        logger.info('Start Auctions Bridge with feed to couchdb',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        logger.info('Start data sync...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        self.planned_tenders = {}
        self.last_seq_id = 0
        while True:
            do_until_success(self.handle_continuous_feed)

    def handle_continuous_feed(self):
        change = self.db.changes(feed='continuous', filter="auctions/by_startDate",
                                 since=self.last_seq_id, include_docs=True)
        for auction_item in change:
            if 'id' in auction_item:
                start_date = auction_item['doc']['stages'][0]['start']
                if auction_item['doc'].get("current_stage", "") == -100:
                    continue

                if auction_item['doc'].get("mode", "") == "test":
                    logger.info('Sciped test auction {}'.format(auction_item['id']),
                                extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
                    continue

                if auction_item['id'] in self.planned_tenders and \
                        self.planned_tenders[auction_item['id']] == start_date:
                    logger.debug('Tender {} filtered'.format(auction_item['id']))
                    continue
                logger.info('Tender {} selected for planning'.format(auction_item['id']),
                            extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})

                if "_" in auction_item['id']:
                    tender_id, lot_id = auction_item['id'].split("_")
                else:
                    tender_id = auction_item['id']
                    lot_id = None

                self.start_auction_worker(
                    tender_id, lot_id=lot_id,
                    with_api_version=auction_item['doc'].get('TENDERS_API_VERSION', None)
                )

                self.planned_tenders[auction_item['id']] = start_date
            elif 'last_seq' in auction_item:
                self.last_seq_id = auction_item['last_seq']

        logger.info('Resume data sync...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})

    def run(self):
        logger.info('Start Auctions Bridge',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        self.client.params.update({'opt_fields': 'status,auctionPeriod,lots', 'mode': '_all_'})

        logger.info('Start data sync...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
        while True:
            for planning_data in self.get_teders_list():
                if len(planning_data) == 1:
                    logger.info('Tender {0} selected for planning'.format(*planning_data))
                    self.start_auction_worker(planning_data[0])
                elif len(planning_data) == 2:
                    logger.info('Lot {1} of tender {0} selected for planning'.format(*planning_data))
                    self.start_auction_worker(planning_data[0], lot_id=planning_data[1])
            logger.info('Sleep...',
                        extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})
            sleep(100)
            logger.info('Resume data sync...',
                        extra={'MESSAGE_ID': DATA_BRIDGE_PLANNING})

    def run_re_planning(self):
        self.re_planning = True
        self.tenders_ids_list = []
        # TODO: support mulilots
        self.offset = ''
        logger.info('Start Auctions Bridge for re-planning...',
                    extra={'MESSAGE_ID': DATA_BRIDGE_RE_PLANNING})
        for tender_item in self.get_teders_list(re_planning=True):
            logger.debug('Tender {} selected for re-planning'.format(tender_item))
            for planning_data in self.get_teders_list():
            # TODO: yield (SIMPLE_AUCTION_TYPE, item)
            if len(planning_data) == 1:
                logger.info('Tender {0} selected for planning'.format(*planning_data))
                self.start_auction_worker(planning_data[0])
            elif len(planning_data) == 2:
                logger.info('Lot {1} of tender {0} selected for planning'.format(*planning_data))
                self.start_auction_worker(planning_data[0], lot_id=planning_data[1])
            self.tenders_ids_list.append(tender_item['id'])
            sleep(1)
        logger.info("Re-planning auctions finished",
                    extra={'MESSAGE_ID': DATA_BRIDGE_RE_PLANNING})

def main():
    parser = argparse.ArgumentParser(description='---- Auctions Bridge ----')
    parser.add_argument('config', type=str, help='Path to configuration file')
    parser.add_argument(
        '--re-planning', action='store_true', default=False,
        help='Not ignore auctions which already scheduled')
    parser.add_argument(
        '--planning-with-couch', action='store_true', default=False,
        help='Use couchdb for tenders feed')
    params = parser.parse_args()
    if os.path.isfile(params.config):
        with open(params.config) as config_file_obj:
            config = load(config_file_obj.read())
        logging.config.dictConfig(config)
        if params.planning_with_couch:
            AuctionsDataBridge(config).planning_with_couch()
        elif params.re_planning:
            AuctionsDataBridge(config).run_re_planning()
        else:
            AuctionsDataBridge(config).run()

##############################################################

if __name__ == "__main__":
    main()
<|endoftext|>


<commit_before>#!/usr/bin/python
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# suppressions.py

"""Valgrind-style suppressions for heapchecker reports.

Suppressions are defined as follows:

# optional one-line comments anywhere in the suppressions file.
{
  Toolname:Errortype
  Short description of the error.
  fun:function_name
  fun:wildcarded_fun*_name
  # an ellipsis wildcards zero or more functions in a stack.
  ...
  fun:some_other_function_name
}

Note that only a 'fun:' prefix is allowed, i.e. we can't suppress objects and
source files.

If ran from the command line, suppressions.py does a self-test of the
Suppression class.
"""

import re

ELLIPSIS = '...'

class Suppression(object):
  """This class represents a single stack trace suppression.

  Attributes:
    type: A string representing the error type, e.g. Heapcheck:Leak.
    description: A string representing the error description.
  """

  def __init__(self, kind, description, stack):
    """Inits Suppression.

    stack is a list of function names and/or wildcards.

    Args:
      kind:
      description: Same as class attributes.
      stack: A list of strings.
    """
    self.type = kind
    self.description = description
    self._stack = stack
    re_line = ''
    re_bucket = ''
    for line in stack:
      if line == ELLIPSIS:
        re_line += re.escape(re_bucket)
        re_bucket = ''
        re_line += '(.*\n)*'
      else:
        for char in line:
          if char == '*':
            re_line += re.escape(re_bucket)
            re_bucket = ''
            re_line += '.*'
          else:  # there can't be any '\*'s in a stack trace
            re_bucket += char
        re_line += re.escape(re_bucket)
        re_bucket = ''
        re_line += '\n'
    self._re = re.compile(re_line, re.MULTILINE)

  def Match(self, report):
    """Returns bool indicating whether the suppression matches the given report.

    Args:
      report: list of strings (function names).
    Returns:
      True if the suppression is not empty and matches the report.
    """
    if not self._stack:
      return False
    if self._re.match('\n'.join(report) + '\n'):
      return True
    else:
      return False

class SuppressionError(Exception):
  def __init__(self, filename, line, report=''):
    Exception.__init__(self, filename, line, report)
    self._file = filename
    self._line = line
    self._report = report

  def __str__(self):
    return 'Error reading suppressions from "%s" (line %d): %s.' % (
        self._file, self._line, self._report)

def ReadSuppressionsFromFile(filename):
  """Given a file, returns a list of suppressions."""
  input_file = file(filename, 'r')
  result = []
  cur_descr = ''
  cur_type = ''
  cur_stack = []
  nline = 0
  try:
    for line in input_file:
      nline += 1
      line = line.strip()
      if line.startswith('#'):
        continue
      elif line.startswith('{'):
        pass
      elif line.startswith('}'):
        result.append(Suppression(cur_type, cur_descr, cur_stack))
        cur_descr = ''
        cur_type = ''
        cur_stack = []
      elif not cur_type:
        cur_type = line
        continue
      elif not cur_descr:
        cur_descr = line
        continue
      elif line.startswith('fun:'):
        line = line[4:]
        cur_stack.append(line.strip())
      elif line.startswith(ELLIPSIS):
        cur_stack.append(ELLIPSIS)
      else:
        raise SuppressionError(filename, nline,
                               '"fun:function_name" or "..." expected')
  except SuppressionError:
    input_file.close()
    raise
  return result

def MatchTest():
  """Tests the Suppression.Match() capabilities."""

  def GenSupp(*lines):
    return Suppression('', '', list(lines))
  empty = GenSupp()
  assert not empty.Match([])
  assert not empty.Match(['foo', 'bar'])
  asterisk = GenSupp('*bar')
  assert asterisk.Match(['foobar', 'foobaz'])
  assert not asterisk.Match(['foobaz', 'foobar'])
  ellipsis = GenSupp('...', 'foo')
  assert ellipsis.Match(['foo', 'bar'])
  assert ellipsis.Match(['bar', 'baz', 'foo'])
  assert not ellipsis.Match(['bar', 'baz', 'bah'])
  mixed = GenSupp('...', 'foo*', 'function')
  assert mixed.Match(['foobar', 'foobaz', 'function'])
  assert not mixed.Match(['foobar', 'blah', 'function'])
  at_and_dollar = GenSupp('foo@GLIBC', 'bar@NOCANCEL')
  assert at_and_dollar.Match(['foo@GLIBC', 'bar@NOCANCEL'])
  re_chars = GenSupp('.*')
  assert re_chars.Match(['.foobar'])
  assert not re_chars.Match(['foobar'])
  print 'PASS'

if __name__ == '__main__':
  MatchTest()
<commit_msg>Fix the order in which the suppression reader reads the suppression's description and type. Due to this bug the bot printed "Heapcheck:Leak" instead of the suppressions' types.<commit_after>#!/usr/bin/python
# Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# suppressions.py

"""Valgrind-style suppressions for heapchecker reports.

Suppressions are defined as follows:

# optional one-line comments anywhere in the suppressions file.
{
  Toolname:Errortype
  Short description of the error.
  fun:function_name
  fun:wildcarded_fun*_name
  # an ellipsis wildcards zero or more functions in a stack.
  ...
  fun:some_other_function_name
}

Note that only a 'fun:' prefix is allowed, i.e. we can't suppress objects and
source files.

If ran from the command line, suppressions.py does a self-test of the
Suppression class.
"""

import re

ELLIPSIS = '...'

class Suppression(object):
  """This class represents a single stack trace suppression.

  Attributes:
    type: A string representing the error type, e.g. Heapcheck:Leak.
    description: A string representing the error description.
  """

  def __init__(self, kind, description, stack):
    """Inits Suppression.

    stack is a list of function names and/or wildcards.

    Args:
      kind:
      description: Same as class attributes.
      stack: A list of strings.
    """
    self.type = kind
    self.description = description
    self._stack = stack
    re_line = ''
    re_bucket = ''
    for line in stack:
      if line == ELLIPSIS:
        re_line += re.escape(re_bucket)
        re_bucket = ''
        re_line += '(.*\n)*'
      else:
        for char in line:
          if char == '*':
            re_line += re.escape(re_bucket)
            re_bucket = ''
            re_line += '.*'
          else:  # there can't be any '\*'s in a stack trace
            re_bucket += char
        re_line += re.escape(re_bucket)
        re_bucket = ''
        re_line += '\n'
    self._re = re.compile(re_line, re.MULTILINE)

  def Match(self, report):
    """Returns bool indicating whether the suppression matches the given report.

    Args:
      report: list of strings (function names).
    Returns:
      True if the suppression is not empty and matches the report.
    """
    if not self._stack:
      return False
    if self._re.match('\n'.join(report) + '\n'):
      return True
    else:
      return False

class SuppressionError(Exception):
  def __init__(self, filename, line, report=''):
    Exception.__init__(self, filename, line, report)
    self._file = filename
    self._line = line
    self._report = report

  def __str__(self):
    return 'Error reading suppressions from "%s" (line %d): %s.' % (
        self._file, self._line, self._report)

def ReadSuppressionsFromFile(filename):
  """Given a file, returns a list of suppressions."""
  input_file = file(filename, 'r')
  result = []
  cur_descr = ''
  cur_type = ''
  cur_stack = []
  nline = 0
  try:
    for line in input_file:
      nline += 1
      line = line.strip()
      if line.startswith('#'):
        continue
      elif line.startswith('{'):
        pass
      elif line.startswith('}'):
        result.append(Suppression(cur_type, cur_descr, cur_stack))
        cur_descr = ''
        cur_type = ''
        cur_stack = []
      elif not cur_descr:
        cur_descr = line
        continue
      elif not cur_type:
        cur_type = line
        continue
      elif line.startswith('fun:'):
        line = line[4:]
        cur_stack.append(line.strip())
      elif line.startswith(ELLIPSIS):
        cur_stack.append(ELLIPSIS)
      else:
        raise SuppressionError(filename, nline,
                               '"fun:function_name" or "..." expected')
  except SuppressionError:
    input_file.close()
    raise
  return result

def MatchTest():
  """Tests the Suppression.Match() capabilities."""

  def GenSupp(*lines):
    return Suppression('', '', list(lines))
  empty = GenSupp()
  assert not empty.Match([])
  assert not empty.Match(['foo', 'bar'])
  asterisk = GenSupp('*bar')
  assert asterisk.Match(['foobar', 'foobaz'])
  assert not asterisk.Match(['foobaz', 'foobar'])
  ellipsis = GenSupp('...', 'foo')
  assert ellipsis.Match(['foo', 'bar'])
  assert ellipsis.Match(['bar', 'baz', 'foo'])
  assert not ellipsis.Match(['bar', 'baz', 'bah'])
  mixed = GenSupp('...', 'foo*', 'function')
  assert mixed.Match(['foobar', 'foobaz', 'function'])
  assert not mixed.Match(['foobar', 'blah', 'function'])
  at_and_dollar = GenSupp('foo@GLIBC', 'bar@NOCANCEL')
  assert at_and_dollar.Match(['foo@GLIBC', 'bar@NOCANCEL'])
  re_chars = GenSupp('.*')
  assert re_chars.Match(['.foobar'])
  assert not re_chars.Match(['foobar'])
  print 'PASS'

if __name__ == '__main__':
  MatchTest()
<|endoftext|>