<commit_before>import os
import time

import sublime
import sublime_plugin

import SublimeHaskell.autocomplete as Autocomplete
import SublimeHaskell.build as Builder
import SublimeHaskell.info_popup as InfoPop
import SublimeHaskell.internals.backend_mgr as BackendManager
import SublimeHaskell.event_common as EventCommon
import SublimeHaskell.internals.proc_helper as ProcHelper
import SublimeHaskell.internals.settings as Settings
import SublimeHaskell.internals.utils as Utils
import SublimeHaskell.parseoutput as ParseOutput
import SublimeHaskell.sublime_haskell_common as Common
import SublimeHaskell.types as Types
import SublimeHaskell.internals.regexes as Regexs

def plugin_loaded():
    '''All of the good stuff that happens when SublimeHaskell is loaded.
    '''
    cache_path = Common.sublime_haskell_cache_path()
    backend_mgr = BackendManager.BackendManager()

    if not os.path.exists(cache_path):
        os.makedirs(cache_path)

    # Probably already loaded... doesn't hurt to reload. Refresh the backend manager's list of backends, while we're at it.
    Settings.load_settings()
    backend_mgr.get_backends()

    # Register change detection:
    Settings.PLUGIN.add_change_callback('add_to_PATH', ProcHelper.ProcHelper.update_environment)
    Settings.PLUGIN.add_change_callback('add_standard_dirs', ProcHelper.ProcHelper.update_environment)
    Settings.PLUGIN.add_change_callback('backends', backend_mgr.updated_settings)

def plugin_unloaded():
    '''Finalization actions when SublimeHaskell is unloaded.
    '''
    ParseOutput.MARKER_MANAGER.clear_error_marks()
    BackendManager.BackendManager().shutdown_backend()

HAS_VIEWEV_API = int(sublime.version()) >= 3155
if HAS_VIEWEV_API:
    EV_SUBCLASS = sublime_plugin.ViewEventListener
else:
    EV_SUBCLASS = sublime_plugin.EventListener

class SublimeHaskellEventListener(EV_SUBCLASS):
    COMPLETION_CHARS = {'is_module_completion': '.',
                        'is_import_completion': '('}

    def context_haskell_autofix(self, view, _key, _operator, _operand, _matchall):
        return view.settings().get('autofix', False)

    def context_auto_completion_popup(self, _view, _key, _operator, _operand, _matchall):
        return Settings.PLUGIN.auto_completion_popup

    def context_haskell_source(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_source(view)

    def context_haskell_source_or_repl(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_source(view) or Common.view_is_haskell_repl(view)

    def context_haskell_repl(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_repl(view)

    def context_haskell_symbol_info(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_symbol_info(view)

    def context_cabal_source(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_cabal_source(view)

    def context_scanned_source(self, view, _key, _operator, _operand, _matchall):
        return self.is_scanned_source(view)

    def context_in_project(self, view, _key, _operator, _operand, _matchall):
        return self.is_in_project(view)

    def context_completion(self, view, key, _operator, _operand, _matchall):
        # Completion context is the only branch here where a backend is needed.
        retval = False
        with self.backend_mgr:
            project_dir, project_name = Common.locate_cabal_project_from_view(view)
            region = view.sel()[0]
            if region.a == region.b:
                word_region = view.word(region)
                preline = Common.get_line_contents_before_region(view, word_region)
                preline += self.COMPLETION_CHARS[key]
                qsymbol = Common.get_qualified_symbol(preline)
                if qsymbol.module:
                    mod_completions = self.autocompleter.get_current_module_completions(project_name, project_dir)
                    if qsymbol.is_import_list:
                        retval = qsymbol.module in mod_completions
                    else:
                        retval = [m for m in mod_completions if m.startswith(qsymbol.module)] != []

        return retval

    CONTEXT_DISPATCH = {'haskell_autofix': context_haskell_autofix,
                        'auto_completion_popup': context_auto_completion_popup,
                        'haskell_source': context_haskell_source,
                        'haskell_source_or_repl': context_haskell_source_or_repl,
                        'haskell_repl': context_haskell_repl,
                        'haskell_symbol_info': context_haskell_symbol_info,
                        'cabal_source': context_cabal_source,
                        'scanned_source': context_scanned_source,
                        'in_project': context_in_project,
                        'is_module_completion': context_completion,
                        'is_import_completion': context_completion}
    '''Context-to-function dispatch dictionary. Used in py:method:`on_qeuery_context` to reduce the branch overhead
    incurred by testing against each key.
    '''

    if HAS_VIEWEV_API:
        @classmethod
        def is_applicable(cls, settings):
            return Common.settings_has_haskell_source(settings)

        @classmethod
        def applies_to_primary_view_only(cls):
            return False

        ## I hate this duplication! But the ST3 developers changed the ViewEventListener and EventListener APIs
        ## within a major version.
        def __init__(self, view):
            super().__init__(view)
            self.autocompleter = Autocomplete.AutoCompleter()
            self.backend_mgr = BackendManager.BackendManager()
            self.type_cache = Types.SourceHaskellTypeCache()

        def on_new(self):
            self.do_new(self.view)

        def on_load(self):
            self.do_load(self.view)

        def on_post_save(self):
            self.do_post_save(self.view)

        def on_query_context(self, key, operator, operand, matchall):
            self.do_query_context(self.view, key, operator, operand, matchall)
    else:
        def __init__(self):
            super().__init__()
            self.autocompleter = Autocomplete.AutoCompleter()
            self.backend_mgr = BackendManager.BackendManager()
            self.type_cache = Types.SourceHaskellTypeCache()

        def on_new(self, view):
            self.do_new(view)

        def on_load(self, view):
            self.do_load(view)

        def on_post_save(self, view):
            self.do_post_save(view)

        def on_query_context(self, view, key, operator, operand, matchall):
            self.do_query_context(view, key, operator, operand, matchall)

    def do_new(self, view):
        filename = view.file_name()
        if not Common.view_is_haskell_source(view) or not filename:
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_new invoked.'.format(type(self).__name__))

        EventCommon.assoc_to_project(view, self.backend_mgr, filename)
        _project_dir, project_name = Common.locate_cabal_project_from_view(view)
        Utils.run_async('rescan {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename,
                        {'drop_all': True})
        view.settings().set('translate_tabs_to_spaces', True)

    def do_load(self, view):
        filename = view.file_name()
        if not Common.view_is_haskell_source(view) or not filename:
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_load {1}.'.format(type(self).__name__, filename))

        view_settings = view.settings() or {}
        if (Settings.PLUGIN.use_improved_syntax and (name.endswith(".hs") or name.endswith(".hsc"))) or \
           view_settings.get('syntax', '').endswith('.tmLanguage'):
            view_settings.set('syntax', 'Packages/SublimeHaskell/Syntaxes/Haskell-SublimeHaskell.sublime-syntax')

        EventCommon.assoc_to_project(view, self.backend_mgr, filename)
        _project_dir, project_name = Common.locate_cabal_project_from_view(view)
        Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename,
                        {'drop_all': False})

    def do_post_save(self, view):
        if not Common.view_is_inspected_source(view):
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_post_save invoked.'.format(type(self).__name__))

        filename = view.file_name()
        if not filename:
            if Settings.COMPONENT_DEBUG.event_viewer:
                print('{0}.on_post_save: no file name.'.format(type(self).__name__))
            return

        _project_dir, project_name = Common.locate_cabal_project_from_view(view)
        if Common.view_is_haskell_source(view):
            self.type_cache.remove(filename)

            if Settings.PLUGIN.enable_auto_build:
                Builder.Builder(view, continue_success=self.post_successful_check).auto_build()
            else:
                EventCommon.do_check_lint(view, continue_success=self.post_successful_check)

        Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name,
                        filename, False)

    def do_query_context(self, view, key, operator, operand, matchall):
        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_query_context key = {1}.'.format(type(self).__name__, key))

        dispatch = self.CONTEXT_DISPATCH.get(key)
        if dispatch:
            ## print('on_query_context: {0}({1}, {2}, {3}, {4})'.format(dispatch.__name__, key, operator, operand, matchall))
            return dispatch(self, view, key, operator, operand, matchall)

        return None

    def rescan_source(self, project_name, filename, drop_all=True):
        if Settings.COMPONENT_DEBUG.inspection:
            print('{0}.rescan_source: {1}/{2}'.format(type(self).__name__, project_name, filename))
        with self.backend_mgr:
            with self.backend_mgr.inspector() as insp:
                if not filename.endswith('.cabal'):
                    insp.mark_file_dirty(filename)
                else:
                    insp.mark_cabal_dirty(filename)

        EventCommon.update_completions_async(self.autocompleter, project_name, [filename], drop_all)

    def is_in_project(self, view):
        file_shown_in_view = Common.window_view_and_file(view)[2]
        if file_shown_in_view is None:
            return False

        src_module = Utils.head_of(BackendManager.active_backend().module(file=file_shown_in_view))
        return src_module is not None and src_module.location.project is not None

    def is_scanned_source(self, view):
        file_shown_in_view = Common.window_view_and_file(view)[2]
        return file_shown_in_view is not None and \
               Utils.head_of(BackendManager.active_backend().module(file=file_shown_in_view)) is not None

    def post_successful_check(self, view):
        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.post_successful_check invoked.'.format(type(self).__name__))

        Types.refresh_view_types(view)

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.post_successful_check: prettify_on_save {0}'.format(Settings.PLUGIN.prettify_on_save))

        if Settings.PLUGIN.prettify_on_save:
            if Settings.PLUGIN.prettify_executable == 'stylish-haskell':
                view.run_command('sublime_haskell_stylish')
            elif Settings.PLUGIN.prettify_executable == 'hindent':
                view.run_command('sublime_haskell_hindent')

## Not needed at present.
##
# class CabalSourceViewEventListener(sublime_plugin.ViewEventListener):
#     @classmethod
#     def is_applicable(cls, settings):
#         return Common.settings_has_cabal_source(settings)
#
#
#     @classmethod
#     def applies_to_primary_view_only(cls):
#         return False
<commit_msg>Issues 414, 416: no autocomplete, no hover<commit_after>import os
import time

import sublime
import sublime_plugin

import SublimeHaskell.autocomplete as Autocomplete
import SublimeHaskell.build as Builder
import SublimeHaskell.info_popup as InfoPop
import SublimeHaskell.internals.backend_mgr as BackendManager
import SublimeHaskell.event_common as EventCommon
import SublimeHaskell.internals.proc_helper as ProcHelper
import SublimeHaskell.internals.settings as Settings
import SublimeHaskell.internals.utils as Utils
import SublimeHaskell.parseoutput as ParseOutput
import SublimeHaskell.sublime_haskell_common as Common
import SublimeHaskell.types as Types
import SublimeHaskell.internals.regexes as Regexs

def plugin_loaded():
    '''All of the good stuff that happens when SublimeHaskell is loaded.
    '''
    cache_path = Common.sublime_haskell_cache_path()
    backend_mgr = BackendManager.BackendManager()

    if not os.path.exists(cache_path):
        os.makedirs(cache_path)

    # Probably already loaded... doesn't hurt to reload. Refresh the backend manager's list of backends, while we're at it.
    Settings.load_settings()
    backend_mgr.get_backends()

    # Register change detection:
    Settings.PLUGIN.add_change_callback('add_to_PATH', ProcHelper.ProcHelper.update_environment)
    Settings.PLUGIN.add_change_callback('add_standard_dirs', ProcHelper.ProcHelper.update_environment)
    Settings.PLUGIN.add_change_callback('backends', backend_mgr.updated_settings)

def plugin_unloaded():
    '''Finalization actions when SublimeHaskell is unloaded.
    '''
    ParseOutput.MARKER_MANAGER.clear_error_marks()
    BackendManager.BackendManager().shutdown_backend()

HAS_VIEWEV_API = int(sublime.version()) >= 3155
if HAS_VIEWEV_API:
    EV_SUBCLASS = sublime_plugin.ViewEventListener
else:
    EV_SUBCLASS = sublime_plugin.EventListener

class SublimeHaskellEventListener(EV_SUBCLASS):
    COMPLETION_CHARS = {'is_module_completion': '.',
                        'is_import_completion': '('}

    def context_haskell_autofix(self, view, _key, _operator, _operand, _matchall):
        return view.settings().get('autofix', False)

    def context_auto_completion_popup(self, _view, _key, _operator, _operand, _matchall):
        return Settings.PLUGIN.auto_completion_popup

    def context_haskell_source(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_source(view)

    def context_haskell_source_or_repl(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_source(view) or Common.view_is_haskell_repl(view)

    def context_haskell_repl(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_repl(view)

    def context_haskell_symbol_info(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_haskell_symbol_info(view)

    def context_cabal_source(self, view, _key, _operator, _operand, _matchall):
        return Common.view_is_cabal_source(view)

    def context_scanned_source(self, view, _key, _operator, _operand, _matchall):
        return self.is_scanned_source(view)

    def context_in_project(self, view, _key, _operator, _operand, _matchall):
        return self.is_in_project(view)

    def context_completion(self, view, key, _operator, _operand, _matchall):
        # Completion context is the only branch here where a backend is needed.
        retval = False
        with self.backend_mgr:
            project_dir, project_name = Common.locate_cabal_project_from_view(view)
            region = view.sel()[0]
            if region.a == region.b:
                word_region = view.word(region)
                preline = Common.get_line_contents_before_region(view, word_region)
                preline += self.COMPLETION_CHARS[key]
                qsymbol = Common.get_qualified_symbol(preline)
                if qsymbol.module:
                    mod_completions = self.autocompleter.get_current_module_completions(project_name, project_dir)
                    if qsymbol.is_import_list:
                        retval = qsymbol.module in mod_completions
                    else:
                        retval = [m for m in mod_completions if m.startswith(qsymbol.module)] != []

        return retval

    CONTEXT_DISPATCH = {'haskell_autofix': context_haskell_autofix,
                        'auto_completion_popup': context_auto_completion_popup,
                        'haskell_source': context_haskell_source,
                        'haskell_source_or_repl': context_haskell_source_or_repl,
                        'haskell_repl': context_haskell_repl,
                        'haskell_symbol_info': context_haskell_symbol_info,
                        'cabal_source': context_cabal_source,
                        'scanned_source': context_scanned_source,
                        'in_project': context_in_project,
                        'is_module_completion': context_completion,
                        'is_import_completion': context_completion}
    '''Context-to-function dispatch dictionary. Used in py:method:`on_qeuery_context` to reduce the branch overhead
    incurred by testing against each key.
    '''

    if HAS_VIEWEV_API:
        @classmethod
        def is_applicable(cls, settings):
            return Common.settings_has_haskell_source(settings)

        @classmethod
        def applies_to_primary_view_only(cls):
            return False

        ## I hate this duplication! But the ST3 developers changed the ViewEventListener and EventListener APIs
        ## within a major version.
        def __init__(self, view):
            super().__init__(view)
            self.autocompleter = Autocomplete.AutoCompleter()
            self.backend_mgr = BackendManager.BackendManager()
            self.type_cache = Types.SourceHaskellTypeCache()

        def on_new(self):
            self.do_new(self.view)

        def on_load(self):
            self.do_load(self.view)

        def on_post_save(self):
            self.do_post_save(self.view)

        def on_query_context(self, key, operator, operand, matchall):
            self.do_query_context(self.view, key, operator, operand, matchall)

        def on_activated(self):
            self.do_activated(self.view, self.view.file_name())

        def on_modified(self):
            self.do_modified(self.view, self.view.file_name())

        def on_hover(self, point, hover_zone):
            self.do_hover(self.view, point, hover_zone)

        def on_query_completions(self, prefix, locations):
            self.do_completions(self.view, prefix, locations)
    else:
        def __init__(self):
            super().__init__()
            self.autocompleter = Autocomplete.AutoCompleter()
            self.backend_mgr = BackendManager.BackendManager()
            self.type_cache = Types.SourceHaskellTypeCache()

        def on_new(self, view):
            self.do_new(view)

        def on_load(self, view):
            self.do_load(view)

        def on_post_save(self, view):
            self.do_post_save(view)

        def on_query_context(self, view, key, operator, operand, matchall):
            self.do_query_context(view, key, operator, operand, matchall)

        def on_activated(self, view):
            self.do_activated(view, view.file_name())

        def on_modified(self, view):
            self.do_modified(view, view.file_name())

        def on_hover(self, view, point, hover_zone):
            self.do_hover(view, point, hover_zone)

        def on_query_completions(self, view, prefix, locations):
            return self.do_query_completions(view, prefix, locations)

    def do_new(self, view):
        filename = view.file_name()
        if not Common.view_is_haskell_source(view) or not filename:
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_new invoked.'.format(type(self).__name__))

        EventCommon.assoc_to_project(view, self.backend_mgr, filename)
        _project_dir, project_name = Common.locate_cabal_project_from_view(view)
        Utils.run_async('rescan {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename,
                        {'drop_all': True})
        view.settings().set('translate_tabs_to_spaces', True)

    def do_load(self, view):
        filename = view.file_name()
        if not Common.view_is_haskell_source(view) or not filename:
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_load {1}.'.format(type(self).__name__, filename))

        view_settings = view.settings() or {}
        if Settings.PLUGIN.use_improved_syntax and (filename.endswith(".hs") or filename.endswith(".hsc") or \
           view_settings.get('syntax', '').endswith('.tmLanguage')):
            view_settings.set('syntax', 'Packages/SublimeHaskell/Syntaxes/Haskell-SublimeHaskell.sublime-syntax')

        EventCommon.assoc_to_project(view, self.backend_mgr, filename)
        _project_dir, project_name = Common.locate_cabal_project_from_view(view)
        Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name, filename,
                        {'drop_all': False})

    def do_post_save(self, view):
        if not Common.view_is_inspected_source(view):
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_post_save invoked.'.format(type(self).__name__))

        filename = view.file_name()
        if not filename:
            if Settings.COMPONENT_DEBUG.event_viewer:
                print('{0}.on_post_save: no file name.'.format(type(self).__name__))
            return

        _project_dir, project_name = Common.locate_cabal_project_from_view(view)
        if Common.view_is_haskell_source(view):
            self.type_cache.remove(filename)

            if Settings.PLUGIN.enable_auto_build:
                Builder.Builder(view, continue_success=self.post_successful_check).auto_build()
            else:
                EventCommon.do_check_lint(view, continue_success=self.post_successful_check)

        Utils.run_async('rescan source {0}/{1}'.format(project_name, filename), self.rescan_source, project_name,
                        filename, False)

    def do_query_context(self, view, key, operator, operand, matchall):
        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_query_context key = {1}.'.format(type(self).__name__, key))

        dispatch = self.CONTEXT_DISPATCH.get(key)
        if dispatch:
            ## print('on_query_context: {0}({1}, {2}, {3}, {4})'.format(dispatch.__name__, key, operator, operand, matchall))
            return dispatch(self, view, key, operator, operand, matchall)

        return None

    def do_activated(self, view, filename):
        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.on_activated invoked.'.format(type(self).__name__))
        Utils.run_async('on_activated', self.activated_worker, view, filename)

    def do_modified(self, _view, filename):
        if not filename:
            return

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0} invoked.'.format(type(self).__name__ + ".on_modified"))

        self.type_cache.remove(filename)

    def do_hover(self, view, point, hover_zone):
        # Note: view.file_name() is not set in certain views, such as the "Haskell Show Types Panel". Avoid
        # generating lookup errors, which are logged in the console window (for better or worse.)
        if view.file_name():
            # Ensure that we never block the Python main thread.
            info_pop = InfoPop.SublimeHaskellHoverPopup(view, view.file_name(), point, hover_zone)
            Utils.run_async('SublimeHaskellPopup.on_hover', info_pop.do_hover)

    def do_query_completions(self, view, prefix, locations):
        # Defer starting the backend until as late as possible...
        if Settings.COMPONENT_DEBUG.event_viewer or Settings.COMPONENT_DEBUG.completions:
            print('{0} invoked (prefix: {1}).'.format(type(self).__name__ + '.on_query_completions', prefix))

        completions = None
        with self.backend_mgr:
            begin_time = time.clock()

            # Only suggest symbols if the current file is part of a Cabal project.
            filename = view.file_name()
            line_contents = Common.get_line_contents(view, locations[0])
            project_name = Common.locate_cabal_project_from_view(view)[1]
            completion_flags = 0
            if not Settings.PLUGIN.add_word_completions:
                completion_flags = completion_flags | sublime.INHIBIT_WORD_COMPLETIONS
            if not Settings.PLUGIN.add_default_completions:
                completion_flags = completion_flags | sublime.INHIBIT_EXPLICIT_COMPLETIONS

            curselector = view.scope_name(locations[0])
            if Regexs.LANGUAGE_RE.search(line_contents):
                completions = self.autocompleter.get_lang_completions(project_name)
            elif Regexs.OPTIONS_GHC_RE.search(line_contents):
                completions = self.autocompleter.get_flag_completions(project_name)
            elif 'meta.import.haskell' in curselector:
                # Inside an import: Complete the imported module name:
                completions = self.autocompleter.get_import_completions(project_name, filename, locations, line_contents)
            elif 'meta.declaration.exports.haskell' in curselector:
                # Export list
                export_module = Autocomplete.EXPORT_MODULE_RE.search(line_contents)
                if export_module:
                    # qsymbol = Common.get_qualified_symbol_at_region(view, view.sel()[0])
                    # TODO: Implement
                    pass
            else:
                # Add current file's completions:
                completions = self.autocompleter.get_completions(view, locations)

            end_time = time.clock()
            if Settings.COMPONENT_DEBUG.event_viewer or Settings.COMPONENT_DEBUG.completions:
                print('time to get completions: {0} seconds'.format(end_time - begin_time))
                print('completion flag: {0}'.format(completion_flags))

        # Don't put completions with special characters (?, !, ==, etc.)
        # into completion because that wipes all default Sublime completions:
        # See http://www.sublimetext.com/forum/viewtopic.php?t=8659
        # TODO: work around this
        # comp = [c for c in completions if NO_SPECIAL_CHARS_RE.match(c[0].split('\t')[0])]
        # if Settings.PLUGIN.inhibit_completions and len(comp) != 0:
        #     return (comp, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS)
        # return comp

        return (completions, completion_flags) # if completions else None

    def rescan_source(self, project_name, filename, drop_all=True):
        if Settings.COMPONENT_DEBUG.inspection:
            print('{0}.rescan_source: {1}/{2}'.format(type(self).__name__, project_name, filename))
        with self.backend_mgr:
            with self.backend_mgr.inspector() as insp:
                if not filename.endswith('.cabal'):
                    insp.mark_file_dirty(filename)
                else:
                    insp.mark_cabal_dirty(filename)

        EventCommon.update_completions_async(self.autocompleter, project_name, [filename], drop_all)

    def is_in_project(self, view):
        file_shown_in_view = Common.window_view_and_file(view)[2]
        if file_shown_in_view is None:
            return False

        src_module = Utils.head_of(BackendManager.active_backend().module(file=file_shown_in_view))
        return src_module is not None and src_module.location.project is not None

    def is_scanned_source(self, view):
        file_shown_in_view = Common.window_view_and_file(view)[2]
        return file_shown_in_view is not None and \
               Utils.head_of(BackendManager.active_backend().module(file=file_shown_in_view)) is not None

    def post_successful_check(self, view):
        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.post_successful_check invoked.'.format(type(self).__name__))

        Types.refresh_view_types(view)

        if Settings.COMPONENT_DEBUG.event_viewer:
            print('{0}.post_successful_check: prettify_on_save {0}'.format(Settings.PLUGIN.prettify_on_save))

        if Settings.PLUGIN.prettify_on_save:
            if Settings.PLUGIN.prettify_executable == 'stylish-haskell':
                view.run_command('sublime_haskell_stylish')
            elif Settings.PLUGIN.prettify_executable == 'hindent':
                view.run_command('sublime_haskell_hindent')

    def activated_worker(self, view, filename):
        with self.backend_mgr:
            EventCommon.assoc_to_project(view, self.backend_mgr, filename)
            _, project_name = Common.locate_cabal_project_from_view(view)
            if Common.view_is_haskell_source(view):
                self.autocompleter.generate_completions_cache(project_name, filename)

## Not needed at present.
##
# class CabalSourceViewEventListener(sublime_plugin.ViewEventListener):
#     @classmethod
#     def is_applicable(cls, settings):
#         return Common.settings_has_cabal_source(settings)
#
#
#     @classmethod
#     def applies_to_primary_view_only(cls):
#         return False
<|endoftext|>


<commit_before>import types
import sys
import asyncio
import pytest
import collections.abc

from . import (
    async_generator, yield_, yield_from_, isasyncgen, isasyncgenfunction,
)

# like list(it) but works on async iterators
async def collect(ait):
    items = []
    async for value in ait:
        items.append(value)
    return items

################################################################
#
# Basic test
#
################################################################

@async_generator
async def async_range(count):
    for i in range(count):
        print("Calling yield_({})".format(i))
        await yield_(i)

@async_generator
async def double(ait):
    async for value in ait:
        await yield_(value * 2)
        await asyncio.sleep(0.001)

class HasAsyncGenMethod:
    def __init__(self, factor):
        self._factor = factor

    @async_generator
    async def async_multiplied(self, ait):
        async for value in ait:
            await yield_(value * self._factor)

@pytest.mark.asyncio
async def test_async_generator():
    assert await collect(async_range(10)) == list(range(10))
    assert (await collect(double(async_range(5)))
            == [0, 2, 4, 6, 8])

    tripler = HasAsyncGenMethod(3)
    assert (await collect(tripler.async_multiplied(async_range(5)))
            == [0, 3, 6, 9, 12])

################################################################
#
# async_generators return value
#
################################################################

@async_generator
async def async_gen_with_non_None_return():
    await yield_(1)
    await yield_(2)
    return "hi"

@pytest.mark.asyncio
async def test_bad_return_value():
    gen = async_gen_with_non_None_return()
    async for item in gen:
        assert item == 1
        break
    async for item in gen:
        assert item == 2
        break
    try:
        await gen.__anext__()
    except StopAsyncIteration as e:
        assert e.args[0] == "hi"

################################################################
#
# Exhausitve tests of the different ways to re-enter a coroutine.
#
# It used to be that re-entering via send/__next__ would work, but throw()
# immediately followed by an await yield_(...)  wouldn't work, and the
# YieldWrapper object would propagate back out to the coroutine runner.
#
# Before I fixed this, the 'assert value is None' check below would fail
# (because of the YieldWrapper leaking out), and if you removed that
# assertion, then the code would appear to run successfully but the final list
# would just be [1, 3] instead of [1, 2, 3].
#
################################################################

class MyTestError(Exception):
    pass

# This unconditionally raises a MyTestError exception, so from the outside
# it's equivalent to a simple 'raise MyTestError`. But, for this test to check
# the thing we want it to check, the point is that the exception must be
# thrown in from the coroutine runner -- this simulates something like an
# 'await sock.recv(...) -> TimeoutError'.
@types.coroutine
def hit_me():
    yield "hit me"

@types.coroutine
def number_me():
    assert (yield "number me") == 1

@types.coroutine
def next_me():
    assert (yield "next me") is None

@async_generator
async def yield_after_different_entries():
    await yield_(1)
    try:
        await hit_me()
    except MyTestError:
        await yield_(2)
    await number_me()
    await yield_(3)
    await next_me()
    await yield_(4)

def hostile_coroutine_runner(coro):
    coro_iter = coro.__await__()
    value = None
    while True:
        try:
            if value == "hit me":
                value = coro_iter.throw(MyTestError())
            elif value == "number me":
                value = coro_iter.send(1)
            else:
                assert value in (None, "next me")
                value = coro_iter.__next__()
        except StopIteration as exc:
            return exc.value

def test_yield_different_entries():
    coro = collect(yield_after_different_entries())
    yielded = hostile_coroutine_runner(coro)
    assert yielded == [1, 2, 3, 4]

################################################################
#
# asend
#
################################################################

@async_generator
async def asend_me():
    assert (await yield_(1)) == 2
    assert (await yield_(3)) == 4

@pytest.mark.asyncio
async def test_asend():
    aiter = asend_me()
    assert (await aiter.__anext__()) == 1
    assert (await aiter.asend(2)) == 3
    with pytest.raises(StopAsyncIteration):
        await aiter.asend(4)

################################################################
#
# athrow
#
################################################################

@async_generator
async def athrow_me():
    with pytest.raises(KeyError):
        await yield_(1)
    with pytest.raises(ValueError):
        await yield_(2)
    await yield_(3)

@pytest.mark.asyncio
async def test_athrow():
    aiter = athrow_me()
    assert (await aiter.__anext__()) == 1
    assert (await aiter.athrow(KeyError("oops"))) == 2
    assert (await aiter.athrow(ValueError("oops"))) == 3
    with pytest.raises(OSError):
        await aiter.athrow(OSError("oops"))

################################################################
#
# aclose
#
################################################################

@async_generator
async def close_me_aiter(track):
    try:
        await yield_(1)
    except GeneratorExit:
        track[0] = "closed"
        raise
    else:  # pragma: no cover
        track[0] = "wtf"

@pytest.mark.asyncio
async def test_aclose():
    track = [None]
    aiter = close_me_aiter(track)
    async for obj in aiter:
        assert obj == 1
        break
    assert track[0] is None
    await aiter.aclose()
    assert track[0] == "closed"

@pytest.mark.asyncio
async def test_aclose_on_unstarted_generator():
    aiter = close_me_aiter([None])
    await aiter.aclose()
    async for obj in aiter:
        assert False  # pragma: no cover

@pytest.mark.asyncio
async def test_aclose_on_finished_generator():
    aiter = async_range(3)
    async for obj in aiter:
        pass  # pragma: no cover
    await aiter.aclose()

@async_generator
async def sync_yield_during_aclose():
    try:
        await yield_(1)
    finally:
        await asyncio.sleep(0)

@async_generator
async def async_yield_during_aclose():
    try:
        await yield_(1)
    finally:
        await yield_(2)

@pytest.mark.asyncio
async def test_aclose_yielding():
    aiter = sync_yield_during_aclose()
    assert (await aiter.__anext__()) == 1
    # Doesn't raise:
    await aiter.aclose()

    aiter = async_yield_during_aclose()
    assert (await aiter.__anext__()) == 1
    with pytest.raises(RuntimeError):
        await aiter.aclose()

################################################################
#
# yield from
#
################################################################

@async_generator
async def async_range_twice(count):
    await yield_from_(async_range(count))
    await yield_(None)
    await yield_from_(async_range(count))

if sys.version_info >= (3, 6):
    exec("""
async def native_async_range(count):
    for i in range(count):
        yield i

async def native_async_range_twice(count):
    # make sure yield_from_ works inside a native async generator
    await yield_from_(async_range(count))
    yield None
    # make sure we can yield_from_ a native async generator
    await yield_from_(native_async_range(count))
    """)

@pytest.mark.asyncio
async def test_async_yield_from_():
    assert await collect(async_range_twice(3)) == [
        0, 1, 2, None, 0, 1, 2,
    ]

    if sys.version_info >= (3, 6):
        assert await collect(native_async_range_twice(3)) == [
            0, 1, 2, None, 0, 1, 2,
        ]

@async_generator
async def doubles_sends(value):
    while True:
        value = await yield_(2 * value)

@async_generator
async def wraps_doubles_sends(value):
    await yield_from_(doubles_sends(value))

@pytest.mark.asyncio
async def test_async_yield_from_asend():
    gen = wraps_doubles_sends(10)
    await gen.__anext__() == 20
    assert (await gen.asend(2)) == 4
    assert (await gen.asend(5)) == 10
    assert (await gen.asend(0)) == 0
    await gen.aclose()

@pytest.mark.asyncio
async def test_async_yield_from_athrow():
    gen = async_range_twice(2)
    assert (await gen.__anext__()) == 0
    with pytest.raises(ValueError):
        await gen.athrow(ValueError)

@async_generator
async def returns_1():
    await yield_(0)
    return 1

@async_generator
async def yields_from_returns_1():
    await yield_(await yield_from_(returns_1()))

@pytest.mark.asyncio
async def test_async_yield_from_return_value():
    assert await collect(yields_from_returns_1()) == [0, 1]

# Special cases to get coverage
@pytest.mark.asyncio
async def test_yield_from_empty():
    @async_generator
    async def empty():
        return "done"

    @async_generator
    async def yield_from_empty():
        assert (await yield_from_(empty())) == "done"

    assert await collect(yield_from_empty()) == []

@pytest.mark.asyncio
async def test_yield_from_non_generator():
    class Countdown:
        def __init__(self, count):
            self.count = count
            self.closed = False

        if sys.version_info < (3, 5, 2):
            async def __aiter__(self):
                return self
        else:
            def __aiter__(self):
                return self

        async def __anext__(self):
            self.count -= 1
            if self.count < 0:
                raise StopAsyncIteration("boom")
            return self.count

        async def aclose(self):
            self.closed = True

    @async_generator
    async def yield_from_countdown(count, happenings):
        try:
            c = Countdown(count)
            assert (await yield_from_(c)) == "boom"
        except BaseException as e:
            if c.closed:
                happenings.append("countdown closed")
            happenings.append("raise")
            return e
    h = []
    assert await collect(yield_from_countdown(3, h)) == [2, 1, 0]
    assert h == []

    # Throwing into a yield_from_(object with no athrow) just raises the
    # exception in the generator.
    h = []
    agen = yield_from_countdown(3, h)
    assert await agen.__anext__() == 2
    exc = ValueError("x")
    try:
        await agen.athrow(exc)
    except StopAsyncIteration as e:
        assert e.args[0] is exc
    assert h == ["raise"]

    # Calling aclose on the generator calls aclose on the iterator
    h = []
    agen = yield_from_countdown(3, h)
    assert await agen.__anext__() == 2
    await agen.aclose()
    assert h == ["countdown closed", "raise"]

    # Throwing GeneratorExit into the generator calls *aclose* on the iterator
    # (!)
    h = []
    agen = yield_from_countdown(3, h)
    assert await agen.__anext__() == 2
    exc = GeneratorExit()
    with pytest.raises(StopAsyncIteration):
        await agen.athrow(exc)
    assert h == ["countdown closed", "raise"]

@pytest.mark.asyncio
async def test_yield_from_non_generator_with_no_aclose():
    class Countdown:
        def __init__(self, count):
            self.count = count
            self.closed = False

        if sys.version_info < (3, 5, 2):
            async def __aiter__(self):
                return self
        else:
            def __aiter__(self):
                return self

        async def __anext__(self):
            self.count -= 1
            if self.count < 0:
                raise StopAsyncIteration("boom")
            return self.count

    @async_generator
    async def yield_from_countdown(count):
        return await yield_from_(Countdown(count))

    assert await collect(yield_from_countdown(3)) == [2, 1, 0]

    agen = yield_from_countdown(3)
    assert await agen.__anext__() == 2
    assert await agen.__anext__() == 1
    # It's OK that Countdown has no aclose
    await agen.aclose()

@pytest.mark.asyncio
async def test_yield_from_with_old_style_aiter():
    # old-style 'async def __aiter__' should still work even on newer pythons
    class Countdown:
        def __init__(self, count):
            self.count = count
            self.closed = False

        # This is wrong, that's the point
        async def __aiter__(self):
            return self

        async def __anext__(self):
            self.count -= 1
            if self.count < 0:
                raise StopAsyncIteration("boom")
            return self.count

    @async_generator
    async def yield_from_countdown(count):
        return await yield_from_(Countdown(count))

    assert await collect(yield_from_countdown(3)) == [2, 1, 0]

@pytest.mark.asyncio
async def test_yield_from_athrow_raises_StopAsyncIteration():
    @async_generator
    async def catch():
        try:
            while True:
                await yield_("hi")
        except Exception as exc:
            return ("bye", exc)

    @async_generator
    async def yield_from_catch():
        return await yield_from_(catch())

    agen = yield_from_catch()
    assert await agen.__anext__() == "hi"
    assert await agen.__anext__() == "hi"
    thrown = ValueError("oops")
    try:
        print(await agen.athrow(thrown))
    except StopAsyncIteration as caught:
        assert caught.args == (("bye", thrown),)
    else:
        raise AssertionError  # pragma: no cover

################################################################
# __del__
################################################################

@pytest.mark.asyncio
async def test___del__():
    gen = async_range(10)
    # Hasn't started yet, so no problem
    gen.__del__()

    gen = async_range(10)
    await collect(gen)
    # Exhausted, so no problem
    gen.__del__()

    gen = async_range(10)
    await gen.aclose()
    # Closed, so no problem
    gen.__del__()

    gen = async_range(10)
    await gen.__anext__()
    await gen.aclose()
    # Closed, so no problem
    gen.__del__()

    gen = async_range(10)
    await gen.__anext__()
    # Started, but not exhausted or closed -- big problem
    with pytest.raises(RuntimeError):
        gen.__del__()

################################################################
# introspection
################################################################

def test_isasyncgen():
    assert not isasyncgen(async_range)
    assert isasyncgen(async_range(10))

    if sys.version_info >= (3, 6):
        assert not isasyncgen(native_async_range)
        assert isasyncgen(native_async_range(10))

def test_isasyncgenfunction():
    assert isasyncgenfunction(async_range)
    assert not isasyncgenfunction(list)
    assert not isasyncgenfunction(async_range(10))

    if sys.version_info >= (3, 6):
        assert isasyncgenfunction(native_async_range)
        assert not isasyncgenfunction(native_async_range(10))

def test_collections_abc_AsyncGenerator():
    if hasattr(collections.abc, "AsyncGenerator"):
        assert isinstance(async_range(10), collections.abc.AsyncGenerator)
<commit_msg>Add a test for gh-3<commit_after>import types
import sys
import asyncio
import pytest
import collections.abc

from . import (
    async_generator, yield_, yield_from_, isasyncgen, isasyncgenfunction,
)

# like list(it) but works on async iterators
async def collect(ait):
    items = []
    async for value in ait:
        items.append(value)
    return items

################################################################
#
# Basic test
#
################################################################

@async_generator
async def async_range(count):
    for i in range(count):
        print("Calling yield_({})".format(i))
        await yield_(i)

@async_generator
async def double(ait):
    async for value in ait:
        await yield_(value * 2)
        await asyncio.sleep(0.001)

class HasAsyncGenMethod:
    def __init__(self, factor):
        self._factor = factor

    @async_generator
    async def async_multiplied(self, ait):
        async for value in ait:
            await yield_(value * self._factor)

@pytest.mark.asyncio
async def test_async_generator():
    assert await collect(async_range(10)) == list(range(10))
    assert (await collect(double(async_range(5)))
            == [0, 2, 4, 6, 8])

    tripler = HasAsyncGenMethod(3)
    assert (await collect(tripler.async_multiplied(async_range(5)))
            == [0, 3, 6, 9, 12])

@async_generator
async def agen_yield_no_arg():
    await yield_()

@pytest.mark.asyncio
async def test_yield_no_arg():
    assert await collect(agen_yield_no_arg()) == [None]

################################################################
#
# async_generators return value
#
################################################################

@async_generator
async def async_gen_with_non_None_return():
    await yield_(1)
    await yield_(2)
    return "hi"

@pytest.mark.asyncio
async def test_bad_return_value():
    gen = async_gen_with_non_None_return()
    async for item in gen:
        assert item == 1
        break
    async for item in gen:
        assert item == 2
        break
    try:
        await gen.__anext__()
    except StopAsyncIteration as e:
        assert e.args[0] == "hi"

################################################################
#
# Exhausitve tests of the different ways to re-enter a coroutine.
#
# It used to be that re-entering via send/__next__ would work, but throw()
# immediately followed by an await yield_(...)  wouldn't work, and the
# YieldWrapper object would propagate back out to the coroutine runner.
#
# Before I fixed this, the 'assert value is None' check below would fail
# (because of the YieldWrapper leaking out), and if you removed that
# assertion, then the code would appear to run successfully but the final list
# would just be [1, 3] instead of [1, 2, 3].
#
################################################################

class MyTestError(Exception):
    pass

# This unconditionally raises a MyTestError exception, so from the outside
# it's equivalent to a simple 'raise MyTestError`. But, for this test to check
# the thing we want it to check, the point is that the exception must be
# thrown in from the coroutine runner -- this simulates something like an
# 'await sock.recv(...) -> TimeoutError'.
@types.coroutine
def hit_me():
    yield "hit me"

@types.coroutine
def number_me():
    assert (yield "number me") == 1

@types.coroutine
def next_me():
    assert (yield "next me") is None

@async_generator
async def yield_after_different_entries():
    await yield_(1)
    try:
        await hit_me()
    except MyTestError:
        await yield_(2)
    await number_me()
    await yield_(3)
    await next_me()
    await yield_(4)

def hostile_coroutine_runner(coro):
    coro_iter = coro.__await__()
    value = None
    while True:
        try:
            if value == "hit me":
                value = coro_iter.throw(MyTestError())
            elif value == "number me":
                value = coro_iter.send(1)
            else:
                assert value in (None, "next me")
                value = coro_iter.__next__()
        except StopIteration as exc:
            return exc.value

def test_yield_different_entries():
    coro = collect(yield_after_different_entries())
    yielded = hostile_coroutine_runner(coro)
    assert yielded == [1, 2, 3, 4]

################################################################
#
# asend
#
################################################################

@async_generator
async def asend_me():
    assert (await yield_(1)) == 2
    assert (await yield_(3)) == 4

@pytest.mark.asyncio
async def test_asend():
    aiter = asend_me()
    assert (await aiter.__anext__()) == 1
    assert (await aiter.asend(2)) == 3
    with pytest.raises(StopAsyncIteration):
        await aiter.asend(4)

################################################################
#
# athrow
#
################################################################

@async_generator
async def athrow_me():
    with pytest.raises(KeyError):
        await yield_(1)
    with pytest.raises(ValueError):
        await yield_(2)
    await yield_(3)

@pytest.mark.asyncio
async def test_athrow():
    aiter = athrow_me()
    assert (await aiter.__anext__()) == 1
    assert (await aiter.athrow(KeyError("oops"))) == 2
    assert (await aiter.athrow(ValueError("oops"))) == 3
    with pytest.raises(OSError):
        await aiter.athrow(OSError("oops"))

################################################################
#
# aclose
#
################################################################

@async_generator
async def close_me_aiter(track):
    try:
        await yield_(1)
    except GeneratorExit:
        track[0] = "closed"
        raise
    else:  # pragma: no cover
        track[0] = "wtf"

@pytest.mark.asyncio
async def test_aclose():
    track = [None]
    aiter = close_me_aiter(track)
    async for obj in aiter:
        assert obj == 1
        break
    assert track[0] is None
    await aiter.aclose()
    assert track[0] == "closed"

@pytest.mark.asyncio
async def test_aclose_on_unstarted_generator():
    aiter = close_me_aiter([None])
    await aiter.aclose()
    async for obj in aiter:
        assert False  # pragma: no cover

@pytest.mark.asyncio
async def test_aclose_on_finished_generator():
    aiter = async_range(3)
    async for obj in aiter:
        pass  # pragma: no cover
    await aiter.aclose()

@async_generator
async def sync_yield_during_aclose():
    try:
        await yield_(1)
    finally:
        await asyncio.sleep(0)

@async_generator
async def async_yield_during_aclose():
    try:
        await yield_(1)
    finally:
        await yield_(2)

@pytest.mark.asyncio
async def test_aclose_yielding():
    aiter = sync_yield_during_aclose()
    assert (await aiter.__anext__()) == 1
    # Doesn't raise:
    await aiter.aclose()

    aiter = async_yield_during_aclose()
    assert (await aiter.__anext__()) == 1
    with pytest.raises(RuntimeError):
        await aiter.aclose()

################################################################
#
# yield from
#
################################################################

@async_generator
async def async_range_twice(count):
    await yield_from_(async_range(count))
    await yield_(None)
    await yield_from_(async_range(count))

if sys.version_info >= (3, 6):
    exec("""
async def native_async_range(count):
    for i in range(count):
        yield i

async def native_async_range_twice(count):
    # make sure yield_from_ works inside a native async generator
    await yield_from_(async_range(count))
    yield None
    # make sure we can yield_from_ a native async generator
    await yield_from_(native_async_range(count))
    """)

@pytest.mark.asyncio
async def test_async_yield_from_():
    assert await collect(async_range_twice(3)) == [
        0, 1, 2, None, 0, 1, 2,
    ]

    if sys.version_info >= (3, 6):
        assert await collect(native_async_range_twice(3)) == [
            0, 1, 2, None, 0, 1, 2,
        ]

@async_generator
async def doubles_sends(value):
    while True:
        value = await yield_(2 * value)

@async_generator
async def wraps_doubles_sends(value):
    await yield_from_(doubles_sends(value))

@pytest.mark.asyncio
async def test_async_yield_from_asend():
    gen = wraps_doubles_sends(10)
    await gen.__anext__() == 20
    assert (await gen.asend(2)) == 4
    assert (await gen.asend(5)) == 10
    assert (await gen.asend(0)) == 0
    await gen.aclose()

@pytest.mark.asyncio
async def test_async_yield_from_athrow():
    gen = async_range_twice(2)
    assert (await gen.__anext__()) == 0
    with pytest.raises(ValueError):
        await gen.athrow(ValueError)

@async_generator
async def returns_1():
    await yield_(0)
    return 1

@async_generator
async def yields_from_returns_1():
    await yield_(await yield_from_(returns_1()))

@pytest.mark.asyncio
async def test_async_yield_from_return_value():
    assert await collect(yields_from_returns_1()) == [0, 1]

# Special cases to get coverage
@pytest.mark.asyncio
async def test_yield_from_empty():
    @async_generator
    async def empty():
        return "done"

    @async_generator
    async def yield_from_empty():
        assert (await yield_from_(empty())) == "done"

    assert await collect(yield_from_empty()) == []

@pytest.mark.asyncio
async def test_yield_from_non_generator():
    class Countdown:
        def __init__(self, count):
            self.count = count
            self.closed = False

        if sys.version_info < (3, 5, 2):
            async def __aiter__(self):
                return self
        else:
            def __aiter__(self):
                return self

        async def __anext__(self):
            self.count -= 1
            if self.count < 0:
                raise StopAsyncIteration("boom")
            return self.count

        async def aclose(self):
            self.closed = True

    @async_generator
    async def yield_from_countdown(count, happenings):
        try:
            c = Countdown(count)
            assert (await yield_from_(c)) == "boom"
        except BaseException as e:
            if c.closed:
                happenings.append("countdown closed")
            happenings.append("raise")
            return e
    h = []
    assert await collect(yield_from_countdown(3, h)) == [2, 1, 0]
    assert h == []

    # Throwing into a yield_from_(object with no athrow) just raises the
    # exception in the generator.
    h = []
    agen = yield_from_countdown(3, h)
    assert await agen.__anext__() == 2
    exc = ValueError("x")
    try:
        await agen.athrow(exc)
    except StopAsyncIteration as e:
        assert e.args[0] is exc
    assert h == ["raise"]

    # Calling aclose on the generator calls aclose on the iterator
    h = []
    agen = yield_from_countdown(3, h)
    assert await agen.__anext__() == 2
    await agen.aclose()
    assert h == ["countdown closed", "raise"]

    # Throwing GeneratorExit into the generator calls *aclose* on the iterator
    # (!)
    h = []
    agen = yield_from_countdown(3, h)
    assert await agen.__anext__() == 2
    exc = GeneratorExit()
    with pytest.raises(StopAsyncIteration):
        await agen.athrow(exc)
    assert h == ["countdown closed", "raise"]

@pytest.mark.asyncio
async def test_yield_from_non_generator_with_no_aclose():
    class Countdown:
        def __init__(self, count):
            self.count = count
            self.closed = False

        if sys.version_info < (3, 5, 2):
            async def __aiter__(self):
                return self
        else:
            def __aiter__(self):
                return self

        async def __anext__(self):
            self.count -= 1
            if self.count < 0:
                raise StopAsyncIteration("boom")
            return self.count

    @async_generator
    async def yield_from_countdown(count):
        return await yield_from_(Countdown(count))

    assert await collect(yield_from_countdown(3)) == [2, 1, 0]

    agen = yield_from_countdown(3)
    assert await agen.__anext__() == 2
    assert await agen.__anext__() == 1
    # It's OK that Countdown has no aclose
    await agen.aclose()

@pytest.mark.asyncio
async def test_yield_from_with_old_style_aiter():
    # old-style 'async def __aiter__' should still work even on newer pythons
    class Countdown:
        def __init__(self, count):
            self.count = count
            self.closed = False

        # This is wrong, that's the point
        async def __aiter__(self):
            return self

        async def __anext__(self):
            self.count -= 1
            if self.count < 0:
                raise StopAsyncIteration("boom")
            return self.count

    @async_generator
    async def yield_from_countdown(count):
        return await yield_from_(Countdown(count))

    assert await collect(yield_from_countdown(3)) == [2, 1, 0]

@pytest.mark.asyncio
async def test_yield_from_athrow_raises_StopAsyncIteration():
    @async_generator
    async def catch():
        try:
            while True:
                await yield_("hi")
        except Exception as exc:
            return ("bye", exc)

    @async_generator
    async def yield_from_catch():
        return await yield_from_(catch())

    agen = yield_from_catch()
    assert await agen.__anext__() == "hi"
    assert await agen.__anext__() == "hi"
    thrown = ValueError("oops")
    try:
        print(await agen.athrow(thrown))
    except StopAsyncIteration as caught:
        assert caught.args == (("bye", thrown),)
    else:
        raise AssertionError  # pragma: no cover

################################################################
# __del__
################################################################

@pytest.mark.asyncio
async def test___del__():
    gen = async_range(10)
    # Hasn't started yet, so no problem
    gen.__del__()

    gen = async_range(10)
    await collect(gen)
    # Exhausted, so no problem
    gen.__del__()

    gen = async_range(10)
    await gen.aclose()
    # Closed, so no problem
    gen.__del__()

    gen = async_range(10)
    await gen.__anext__()
    await gen.aclose()
    # Closed, so no problem
    gen.__del__()

    gen = async_range(10)
    await gen.__anext__()
    # Started, but not exhausted or closed -- big problem
    with pytest.raises(RuntimeError):
        gen.__del__()

################################################################
# introspection
################################################################

def test_isasyncgen():
    assert not isasyncgen(async_range)
    assert isasyncgen(async_range(10))

    if sys.version_info >= (3, 6):
        assert not isasyncgen(native_async_range)
        assert isasyncgen(native_async_range(10))

def test_isasyncgenfunction():
    assert isasyncgenfunction(async_range)
    assert not isasyncgenfunction(list)
    assert not isasyncgenfunction(async_range(10))

    if sys.version_info >= (3, 6):
        assert isasyncgenfunction(native_async_range)
        assert not isasyncgenfunction(native_async_range(10))

def test_collections_abc_AsyncGenerator():
    if hasattr(collections.abc, "AsyncGenerator"):
        assert isinstance(async_range(10), collections.abc.AsyncGenerator)
<|endoftext|>


<commit_before>from django.contrib.syndication.views import Feed
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.utils.feedgenerator import Rss201rev2Feed
from django.http import Http404

from string import upper

from portal.models import MediaItem, Channel, Collection, Comment, MediaFile
from portal.media_formats import MEDIA_FORMATS

import lambdaproject.settings as settings

import markdown
from datetime import datetime, time
import os

class iTunesFeed(Rss201rev2Feed):
    def rss_attributes(self):
        return {u"version": self._version, u"xmlns:content": "http://purl.org/rss/1.0/modules/content", u"xmlns:sy": "http://purl.org/rss/1.0/modules/syndication/", u"xmlns:atom": u"http://www.w3.org/2005/Atom", u'xmlns:itunes': u'http://www.itunes.com/dtds/podcast-1.0.dtd'}

    def add_root_elements(self, handler):
        super(iTunesFeed, self).add_root_elements(handler)
        handler.addQuickElement(u'itunes:subtitle', self.feed['subtitle'])
        handler.addQuickElement(u'itunes:author', self.feed['author_name'])
        handler.addQuickElement(u'itunes:summary', self.feed['description'])
        handler.addQuickElement(u'itunes:explicit', self.feed['iTunes_explicit'])
        handler.startElement(u"itunes:owner", {})
        handler.addQuickElement(u'itunes:name', self.feed['iTunes_name'])
        handler.addQuickElement(u'itunes:email', self.feed['iTunes_email'])
        handler.endElement(u"itunes:owner")
        handler.addQuickElement(u'itunes:image', self.feed['iTunes_image_url'])
        handler.startElement('image', {})
        handler.addQuickElement('title', self.feed['title'])
        handler.addQuickElement('url', self.feed['iTunes_image_url'])
        handler.addQuickElement('link', self.feed['img_site_url'])
        handler.endElement('image')

    def add_item_elements(self,  handler, item):
        super(iTunesFeed, self).add_item_elements(handler, item)
        handler.addQuickElement(u'itunes:summary',item['summary'])
        handler.addQuickElement(u'itunes:duration',item['duration'])
        handler.addQuickElement(u'itunes:explicit',item['explicit'])
        handler.addQuickElement(u'itunes:image', item['item_thumb'])

class MediaFeed(Feed):
    feed_type = iTunesFeed
    title = ''
    description = ''
    subtitle = description
    author_name = settings.AUTHOR_NAME

    def get_object(self, request, fileformat):
        fileformat = upper(fileformat)
        if fileformat not in MEDIA_FORMATS:
            raise Http404
        self.fileformat = fileformat

    def items(self):
        return MediaFile.objects.select_related('media_item').filter(media_item__published=True, file_format=self.fileformat).exclude(size__isnull=True).order_by('-media_item__date').order_by('-media_item__created')

    def feed_extra_kwargs(self, obj):
        extra = {}
        extra['iTunes_name'] = settings.AUTHOR_NAME
        extra['iTunes_email'] = settings.CONTACT_EMAIL
        extra['iTunes_image_url'] = settings.LOGO_URL
        extra['iTunes_explicit'] = 'no'
        extra['img_site_url'] = settings.WEBSITE_URL
        return extra

    def item_extra_kwargs(self, item):
        extra = {}
        media_item = item.media_item
        extra['duration'] = str(media_item.duration)
        extra['summary'] = self.item_description(item)
        extra['explicit'] = 'no'
        if media_item.videoThumbURL:
            extra['item_thumb'] = media_item.videoThumbURL
        elif media_item.audioThumbURL:
            extra['item_thumb'] = media_item.audioThumbURL
        elif media_item.channel and media_item.channel.channelThumbURL:
            extra['item_thumb'] = media_item.channel.channelThumbURL
        else:
            extra['item_thumb'] = ''
        return extra

    def item_title(self, item):
        return item.media_item.title

    def item_description(self, item):
        return markdown.markdown(item.media_item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_link(self, item):
        return item.url

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

    def item_enclosure_url(self, item):
        return self.item_link(item)

    def item_enclosure_length(self, item):
        return item.size

    def item_enclosure_mime_type(self, item):
        return MEDIA_FORMATS[self.fileformat].mime_type

class LatestMedia(MediaFeed):
    title = _("%s - Latest Episodes")  % (settings.SITE_NAME)
    link = "/"
    description = _(u"The latest episodes from %s") % (settings.SITE_NAME)

    def items(self):
        return MediaFeed.items(self)[:15]

class TorrentFeed(Feed):
    title = _(u"%s - Latest Episodes (Torrent)") % (settings.SITE_NAME)
    link = "/"
    description = _("Torrent feed for the latest episodes from %s") % (settings.SITE_NAME)
    item_enclosure_mime_type = "application/x-bittorrent"

    def items(self):
        return MediaItem.objects.filter(published=True, torrentDone=True).exclude(torrentURL='').order_by('-date')[:15]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return markdown.markdown(item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_enclosure_url(self, item):
        return item.torrentURL

    def item_enclosure_length(self, item):
        return os.path.getsize(settings.BITTORRENT_FILES_DIR + item.slug + '.torrent')

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

class ChannelFeed(MediaFeed):
    ''' This class (like the next one) gives the feeds for channels"'''
    def get_object(self, request, channel_slug, fileformat):
        MediaFeed.get_object(self, request, fileformat)
        return get_object_or_404(Channel, slug=channel_slug)

    def feed_extra_kwargs(self, obj):
        extra = {}
        extra['iTunes_name'] = settings.AUTHOR_NAME
        extra['iTunes_email'] = settings.CONTACT_EMAIL
        extra['iTunes_image_url'] = obj.channelThumbURL
        extra['iTunes_explicit'] = 'no'
        extra['img_site_url'] = settings.WEBSITE_URL
        return extra

    def title(self, obj):
        return "%s: %s" % (settings.AUTHOR_NAME, obj.name)

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return obj.description

    def items(self, obj):
        return MediaFeed.items(self).filter(media_item__channel=obj)

class ChannelFeedTorrent(Feed):

    def get_object(self, request, channel_slug):
        return get_object_or_404(Channel, slug=channel_slug)

    def title(self, obj):
        return _(u"Torrents for Channel %s") % obj.name

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return  obj.description

    item_enclosure_mime_type = "application/x-bittorrent"

    def items(self, obj):
        return MediaItem.objects.filter(published=True, channel=obj, torrentDone=True ).exclude(torrentURL='').order_by('-date')

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return markdown.markdown(item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_enclosure_url(self, item):
        return item.torrentURL

    def item_enclosure_length(self, item):
        return os.path.getsize(settings.BITTORRENT_FILES_DIR + item.slug + '.torrent')

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

class CollectionFeed(MediaFeed):
    def get_object(self, request, collection_slug, fileformat):
        MediaFeed.get_object(self, request, fileformat)
        return get_object_or_404(Collection, slug=collection_slug)

    def title(self, obj):
        return _(u"Media Items in Collection %s") % obj.title

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return obj.description

    def items(self, obj):
        return MediaFeed.items(self).filter(media_item__in=obj.items.all())

class CollectionFeedTorrent(Feed):

    def get_object(self, request, collection_slug):
        return get_object_or_404(Collection, slug=collection_slug)

    def title(self, obj):
        return _(u"Torrents for items in Collection %s") % obj.title

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return  obj.description

    item_enclosure_mime_type = "application/x-bittorrent"

    def items(self, obj):
        return obj.items.filter(torrentDone=True, published=True).exclude(torrentURL='').order_by('-date')

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return markdown.markdown(item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_enclosure_url(self, item):
        return item.torrentURL

    def item_enclosure_length(self, item):
        return os.path.getsize(settings.BITTORRENT_FILES_DIR + item.slug + '.torrent')

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

class CommentsFeed(Feed):
    title = _(u"Latest comments from %s") % (settings.SITE_NAME)
    link = "/"
    description = _(u"Latest comments from %s") % (settings.SITE_NAME)

    def items(self):
        return Comment.objects.filter(moderated=True).order_by('-created')[:15]

    def item_title(self, comment):
        title = _(u"New comment to %s") % comment.item.title
        return title

    def item_description(self, item):
        return markdown.markdown(item.comment, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_pubdate(self, item):
        return item.created
<commit_msg>LAMBDA-122: entry title links to item page<commit_after>from django.contrib.syndication.views import Feed
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext_lazy as _
from django.utils.feedgenerator import Rss201rev2Feed
from django.http import Http404

from string import upper

from portal.models import MediaItem, Channel, Collection, Comment, MediaFile
from portal.media_formats import MEDIA_FORMATS

import lambdaproject.settings as settings

import markdown
from datetime import datetime, time
import os

class iTunesFeed(Rss201rev2Feed):
    def rss_attributes(self):
        return {u"version": self._version, u"xmlns:content": "http://purl.org/rss/1.0/modules/content", u"xmlns:sy": "http://purl.org/rss/1.0/modules/syndication/", u"xmlns:atom": u"http://www.w3.org/2005/Atom", u'xmlns:itunes': u'http://www.itunes.com/dtds/podcast-1.0.dtd'}

    def add_root_elements(self, handler):
        super(iTunesFeed, self).add_root_elements(handler)
        handler.addQuickElement(u'itunes:subtitle', self.feed['subtitle'])
        handler.addQuickElement(u'itunes:author', self.feed['author_name'])
        handler.addQuickElement(u'itunes:summary', self.feed['description'])
        handler.addQuickElement(u'itunes:explicit', self.feed['iTunes_explicit'])
        handler.startElement(u"itunes:owner", {})
        handler.addQuickElement(u'itunes:name', self.feed['iTunes_name'])
        handler.addQuickElement(u'itunes:email', self.feed['iTunes_email'])
        handler.endElement(u"itunes:owner")
        handler.addQuickElement(u'itunes:image', self.feed['iTunes_image_url'])
        handler.startElement('image', {})
        handler.addQuickElement('title', self.feed['title'])
        handler.addQuickElement('url', self.feed['iTunes_image_url'])
        handler.addQuickElement('link', self.feed['img_site_url'])
        handler.endElement('image')

    def add_item_elements(self,  handler, item):
        super(iTunesFeed, self).add_item_elements(handler, item)
        handler.addQuickElement(u'itunes:summary',item['summary'])
        handler.addQuickElement(u'itunes:duration',item['duration'])
        handler.addQuickElement(u'itunes:explicit',item['explicit'])
        handler.addQuickElement(u'itunes:image', item['item_thumb'])

class MediaFeed(Feed):
    feed_type = iTunesFeed
    title = ''
    description = ''
    subtitle = description
    author_name = settings.AUTHOR_NAME

    def get_object(self, request, fileformat):
        fileformat = upper(fileformat)
        if fileformat not in MEDIA_FORMATS:
            raise Http404
        self.fileformat = fileformat

    def items(self):
        return MediaFile.objects.select_related('media_item').filter(media_item__published=True, file_format=self.fileformat).exclude(size__isnull=True).order_by('-media_item__date').order_by('-media_item__created')

    def feed_extra_kwargs(self, obj):
        extra = {}
        extra['iTunes_name'] = settings.AUTHOR_NAME
        extra['iTunes_email'] = settings.CONTACT_EMAIL
        extra['iTunes_image_url'] = settings.LOGO_URL
        extra['iTunes_explicit'] = 'no'
        extra['img_site_url'] = settings.WEBSITE_URL
        return extra

    def item_extra_kwargs(self, item):
        extra = {}
        media_item = item.media_item
        extra['duration'] = str(media_item.duration)
        extra['summary'] = self.item_description(item)
        extra['explicit'] = 'no'
        if media_item.videoThumbURL:
            extra['item_thumb'] = media_item.videoThumbURL
        elif media_item.audioThumbURL:
            extra['item_thumb'] = media_item.audioThumbURL
        elif media_item.channel and media_item.channel.channelThumbURL:
            extra['item_thumb'] = media_item.channel.channelThumbURL
        else:
            extra['item_thumb'] = ''
        return extra

    def item_title(self, item):
        return item.media_item.title

    def item_description(self, item):
        return markdown.markdown(item.media_item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_link(self, item):
        return '/item/' + item.media_item.slug

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

    def item_enclosure_url(self, item):
        return item.url

    def item_enclosure_length(self, item):
        return item.size

    def item_enclosure_mime_type(self, item):
        return MEDIA_FORMATS[self.fileformat].mime_type

class LatestMedia(MediaFeed):
    title = _("%s - Latest Episodes")  % (settings.SITE_NAME)
    link = "/"
    description = _(u"The latest episodes from %s") % (settings.SITE_NAME)

    def items(self):
        return MediaFeed.items(self)[:15]

class TorrentFeed(Feed):
    title = _(u"%s - Latest Episodes (Torrent)") % (settings.SITE_NAME)
    link = "/"
    description = _("Torrent feed for the latest episodes from %s") % (settings.SITE_NAME)
    item_enclosure_mime_type = "application/x-bittorrent"

    def items(self):
        return MediaItem.objects.filter(published=True, torrentDone=True).exclude(torrentURL='').order_by('-date')[:15]

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return markdown.markdown(item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_enclosure_url(self, item):
        return item.torrentURL

    def item_enclosure_length(self, item):
        return os.path.getsize(settings.BITTORRENT_FILES_DIR + item.slug + '.torrent')

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

class ChannelFeed(MediaFeed):
    ''' This class (like the next one) gives the feeds for channels"'''
    def get_object(self, request, channel_slug, fileformat):
        MediaFeed.get_object(self, request, fileformat)
        return get_object_or_404(Channel, slug=channel_slug)

    def feed_extra_kwargs(self, obj):
        extra = {}
        extra['iTunes_name'] = settings.AUTHOR_NAME
        extra['iTunes_email'] = settings.CONTACT_EMAIL
        extra['iTunes_image_url'] = obj.channelThumbURL
        extra['iTunes_explicit'] = 'no'
        extra['img_site_url'] = settings.WEBSITE_URL
        return extra

    def title(self, obj):
        return "%s: %s" % (settings.AUTHOR_NAME, obj.name)

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return obj.description

    def items(self, obj):
        return MediaFeed.items(self).filter(media_item__channel=obj)

class ChannelFeedTorrent(Feed):

    def get_object(self, request, channel_slug):
        return get_object_or_404(Channel, slug=channel_slug)

    def title(self, obj):
        return _(u"Torrents for Channel %s") % obj.name

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return  obj.description

    item_enclosure_mime_type = "application/x-bittorrent"

    def items(self, obj):
        return MediaItem.objects.filter(published=True, channel=obj, torrentDone=True ).exclude(torrentURL='').order_by('-date')

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return markdown.markdown(item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_enclosure_url(self, item):
        return item.torrentURL

    def item_enclosure_length(self, item):
        return os.path.getsize(settings.BITTORRENT_FILES_DIR + item.slug + '.torrent')

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

class CollectionFeed(MediaFeed):
    def get_object(self, request, collection_slug, fileformat):
        MediaFeed.get_object(self, request, fileformat)
        return get_object_or_404(Collection, slug=collection_slug)

    def title(self, obj):
        return _(u"Media Items in Collection %s") % obj.title

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return obj.description

    def items(self, obj):
        return MediaFeed.items(self).filter(media_item__in=obj.items.all())

class CollectionFeedTorrent(Feed):

    def get_object(self, request, collection_slug):
        return get_object_or_404(Collection, slug=collection_slug)

    def title(self, obj):
        return _(u"Torrents for items in Collection %s") % obj.title

    def link(self, obj):
        return obj.get_absolute_url()

    def description(self, obj):
        return  obj.description

    item_enclosure_mime_type = "application/x-bittorrent"

    def items(self, obj):
        return obj.items.filter(torrentDone=True, published=True).exclude(torrentURL='').order_by('-date')

    def item_title(self, item):
        return item.title

    def item_description(self, item):
        return markdown.markdown(item.description, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_enclosure_url(self, item):
        return item.torrentURL

    def item_enclosure_length(self, item):
        return os.path.getsize(settings.BITTORRENT_FILES_DIR + item.slug + '.torrent')

    def item_pubdate(self, item):
        return datetime.combine(item.media_item.date, time())

class CommentsFeed(Feed):
    title = _(u"Latest comments from %s") % (settings.SITE_NAME)
    link = "/"
    description = _(u"Latest comments from %s") % (settings.SITE_NAME)

    def items(self):
        return Comment.objects.filter(moderated=True).order_by('-created')[:15]

    def item_title(self, comment):
        title = _(u"New comment to %s") % comment.item.title
        return title

    def item_description(self, item):
        return markdown.markdown(item.comment, safe_mode='replace', html_replacement_text='[HTML_REMOVED]')

    def item_pubdate(self, item):
        return item.created
<|endoftext|>


<commit_before>"""Tasks module

All tasks run via external message queue (via celery) are defined
within.

NB: a celery worker must be started for these to ever return.  See
`celery_worker.py`

"""
from sqlalchemy import and_
from datetime import datetime
from flask import current_app
from requests import Request, Session
from requests.exceptions import RequestException
from celery.utils.log import get_task_logger

from .database import db
from .dogpile import dogpile_cache
from .extensions import celery
from .models.assessment_status import overall_assessment_status
from .models.communication import Communication
from .models.communication_request import queue_outstanding_messages
from .models.reporting import get_reporting_stats
from .models.role import Role, ROLE
from .models.questionnaire_bank import QuestionnaireBank
from .models.user import User, UserRoles
from .models.scheduled_job import update_runtime

# To debug, stop the celeryd running out of /etc/init, start in console:
#   celery worker -A portal.celery_worker.celery --loglevel=debug
# Import rdb and use like pdb:
#   from celery.contrib import rdb
#   rdb.set_trace()
# Follow instructions from celery console, i.e. telnet 127.0.0.1 6900

logger = get_task_logger(__name__)

@celery.task
def add(x, y):
    return x + y

@celery.task
def info():
    return "CELERY_BROKER_URL: {}
 SERVER_NAME: {}".format(
        current_app.config.get('CELERY_BROKER_URL'),
        current_app.config.get('SERVER_NAME'))

@celery.task(bind=True)
def post_request(self, url, data, timeout=10, retries=3):
    """Wrap requests.post for asyncronous posts - includes timeout & retry"""
    logger.debug("task: %s retries:%s", self.request.id, self.request.retries)

    s = Session()
    req = Request('POST', url, data=data)
    prepped = req.prepare()
    try:
        resp = s.send(prepped, timeout=timeout)
        if resp.status_code < 400:
            logger.info("{} received from {}".format(resp.status_code, url))
        else:
            logger.error("{} received from {}".format(resp.status_code, url))

    except RequestException as exc:
        """Typically raised on timeout or connection error

        retry after countdown seconds unless retry threshold has been exceeded
        """
        logger.warn("{} on {}".format(exc.message, url))
        if self.request.retries < retries:
            raise self.retry(exc=exc, countdown=20)
        else:
            logger.error(
                "max retries exceeded for {}, last failure: {}".format(
                    url, exc))
    except Exception as exc:
        logger.error("Unexpected exception on {} : {}".format(url, exc))

@celery.task
def test(job_id=None):
    try:
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `test` on {} : {}".format(
            job_id, exc))
    return "Test task complete."

@celery.task
def cache_reporting_stats(job_id=None):
    """Populate reporting dashboard stats cache

    Reporting stats can be a VERY expensive lookup - cached for an hour
    at a time.  This task is responsible for renewing the potenailly
    stale cache.  Expected to be called as a scheduled job.

    """
    try:
        message = "failed"
        before = datetime.now()
        current_app.logger.debug(__name__)
        dogpile_cache.invalidate(get_reporting_stats)
        dogpile_cache.refresh(get_reporting_stats)
        duration = datetime.now() - before
        message = (
            'Reporting stats updated in {0.seconds} seconds'.format(duration))
        current_app.logger.debug(message)
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `cache_reporting_stats` "
                     "on {} : {}".format(job_id, exc))
    return message

@celery.task
def cache_assessment_status(job_id=None):
    """Populate assessment status cache

    Assessment status is an expensive lookup - cached for an hour
    at a time.  This task is responsible for renewing the potenailly
    stale cache.  Expected to be called as a scheduled job.

    """
    try:
        message = "failed"
        before = datetime.now()
        current_app.logger.debug(__name__)
        update_patient_loop(update_cache=True, queue_messages=True)
        duration = datetime.now() - before
        message = (
            'Assessment Cache updated and messages queued in {0.seconds}'
            ' seconds'.format(duration))
        current_app.logger.debug(message)
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `cache_assessment_status` "
                     "on {} : {}".format(job_id, exc))
    return message

def update_patient_loop(update_cache=True, queue_messages=True):
    """Function to loop over valid patients and update as per settings

    Typically called as a scheduled_job - also directly from tests
    """
    patient_role_id = Role.query.filter(
        Role.name == ROLE.PATIENT).with_entities(Role.id).first()[0]
    valid_patients = User.query.join(
        UserRoles).filter(
            and_(User.id == UserRoles.user_id,
                 User.deleted_id.is_(None),
                 UserRoles.role_id == patient_role_id))

    for user in valid_patients:
        if update_cache:
            dogpile_cache.refresh(overall_assessment_status, user.id)
        if queue_messages:
            qbd = QuestionnaireBank.most_current_qb(user=user)
            logger.debug("update_patient_loop user {}".format(user.id))
            if qbd.questionnaire_bank:
                logger.debug(
                    "update_patient_loop w/ questionnaire for user {}".format(
                        user.id))
                queue_outstanding_messages(
                    user=user,
                    questionnaire_bank=qbd.questionnaire_bank,
                    iteration_count=qbd.iteration)
    db.session.commit()

@celery.task
def send_queued_communications(job_id=None):
    "Look for communication objects ready to send"
    send_messages()
    update_runtime(job_id)

def send_messages():
    """Function to send all queued messages

    Typically called as a scheduled_job - also directly from tests
    """
    ready = Communication.query.filter(Communication.status == 'prepared')
    for communication in ready:
        current_app.logger.debug("Collate ready communication {}".format(
            communication))
        communication.generate_and_send()
<commit_msg>Fix log msgs and split out task to prepare_communications.<commit_after>"""Tasks module

All tasks run via external message queue (via celery) are defined
within.

NB: a celery worker must be started for these to ever return.  See
`celery_worker.py`

"""
from sqlalchemy import and_
from datetime import datetime
from flask import current_app
from requests import Request, Session
from requests.exceptions import RequestException
from celery.utils.log import get_task_logger

from .database import db
from .dogpile import dogpile_cache
from .extensions import celery
from .models.assessment_status import overall_assessment_status
from .models.communication import Communication
from .models.communication_request import queue_outstanding_messages
from .models.reporting import get_reporting_stats
from .models.role import Role, ROLE
from .models.questionnaire_bank import QuestionnaireBank
from .models.user import User, UserRoles
from .models.scheduled_job import update_runtime

# To debug, stop the celeryd running out of /etc/init, start in console:
#   celery worker -A portal.celery_worker.celery --loglevel=debug
# Import rdb and use like pdb:
#   from celery.contrib import rdb
#   rdb.set_trace()
# Follow instructions from celery console, i.e. telnet 127.0.0.1 6900

logger = get_task_logger(__name__)

@celery.task
def add(x, y):
    return x + y

@celery.task
def info():
    return "CELERY_BROKER_URL: {}
 SERVER_NAME: {}".format(
        current_app.config.get('CELERY_BROKER_URL'),
        current_app.config.get('SERVER_NAME'))

@celery.task(bind=True)
def post_request(self, url, data, timeout=10, retries=3):
    """Wrap requests.post for asyncronous posts - includes timeout & retry"""
    logger.debug("task: %s retries:%s", self.request.id, self.request.retries)

    s = Session()
    req = Request('POST', url, data=data)
    prepped = req.prepare()
    try:
        resp = s.send(prepped, timeout=timeout)
        if resp.status_code < 400:
            logger.info("{} received from {}".format(resp.status_code, url))
        else:
            logger.error("{} received from {}".format(resp.status_code, url))

    except RequestException as exc:
        """Typically raised on timeout or connection error

        retry after countdown seconds unless retry threshold has been exceeded
        """
        logger.warn("{} on {}".format(exc.message, url))
        if self.request.retries < retries:
            raise self.retry(exc=exc, countdown=20)
        else:
            logger.error(
                "max retries exceeded for {}, last failure: {}".format(
                    url, exc))
    except Exception as exc:
        logger.error("Unexpected exception on {} : {}".format(url, exc))

@celery.task
def test(job_id=None):
    try:
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `test` on {} : {}".format(
            job_id, exc))
    return "Test task complete."

@celery.task
def cache_reporting_stats(job_id=None):
    """Populate reporting dashboard stats cache

    Reporting stats can be a VERY expensive lookup - cached for an hour
    at a time.  This task is responsible for renewing the potenailly
    stale cache.  Expected to be called as a scheduled job.

    """
    try:
        message = "failed"
        before = datetime.now()
        current_app.logger.debug('cache_reporting_stats')
        dogpile_cache.invalidate(get_reporting_stats)
        dogpile_cache.refresh(get_reporting_stats)
        duration = datetime.now() - before
        message = (
            'Reporting stats updated in {0.seconds} seconds'.format(duration))
        current_app.logger.debug(message)
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `cache_reporting_stats` "
                    "on {} : {}".format(job_id, exc))
    return message

@celery.task
def cache_assessment_status(job_id=None):
    """Populate assessment status cache

    Assessment status is an expensive lookup - cached for an hour
    at a time.  This task is responsible for renewing the potenailly
    stale cache.  Expected to be called as a scheduled job.

    """
    try:
        message = "failed"
        before = datetime.now()
        current_app.logger.debug('cache_assessment_status')
        update_patient_loop(update_cache=True, queue_messages=False)
        duration = datetime.now() - before
        message = (
            'Assessment Cache updated and messages queued in {0.seconds}'
            ' seconds'.format(duration))
        current_app.logger.debug(message)
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `cache_assessment_status` "
                    "on {} : {}".format(job_id, exc))
    return message

@celery.task
def prepare_communications(job_id=None):
    """Move any ready communications into prepared state """
    try:
        message = "failed"
        before = datetime.now()
        current_app.logger.debug('prepare_communications')
        update_patient_loop(update_cache=False, queue_messages=True)
        duration = datetime.now() - before
        message = (
            'Assessment Cache updated and messages queued in {0.seconds}'
            ' seconds'.format(duration))
        current_app.logger.debug(message)
        update_runtime(job_id)
    except Exception as exc:
        logger.warn("Unexpected exception in `cache_assessment_status` "
                    "on {} : {}".format(job_id, exc))
    return message

def update_patient_loop(update_cache=True, queue_messages=True):
    """Function to loop over valid patients and update as per settings

    Typically called as a scheduled_job - also directly from tests
    """
    patient_role_id = Role.query.filter(
        Role.name == ROLE.PATIENT).with_entities(Role.id).first()[0]
    valid_patients = User.query.join(
        UserRoles).filter(
            and_(User.id == UserRoles.user_id,
                 User.deleted_id.is_(None),
                 UserRoles.role_id == patient_role_id))

    for user in valid_patients:
        if update_cache:
            dogpile_cache.refresh(overall_assessment_status, user.id)
        if queue_messages:
            qbd = QuestionnaireBank.most_current_qb(user=user)
            logger.debug("update_patient_loop user {}".format(user.id))
            if qbd.questionnaire_bank:
                logger.debug(
                    "update_patient_loop w/ questionnaire for user {}".format(
                        user.id))
                queue_outstanding_messages(
                    user=user,
                    questionnaire_bank=qbd.questionnaire_bank,
                    iteration_count=qbd.iteration)
    db.session.commit()

@celery.task
def send_queued_communications(job_id=None):
    "Look for communication objects ready to send"
    send_messages()
    update_runtime(job_id)

def send_messages():
    """Function to send all queued messages

    Typically called as a scheduled_job - also directly from tests
    """
    ready = Communication.query.filter(Communication.status == 'prepared')
    for communication in ready:
        current_app.logger.debug("Collate ready communication {}".format(
            communication))
        communication.generate_and_send()
<|endoftext|>


<commit_before>#!/usr/bin/env python

"""
Tasks performed after model building
"""

import pickle

NN_PICKLE = 'nn_data.pkl'
SVM_PICKLE = 'svm_data.pkl'

def results():
    """
    Printing results and metrics from the pickles used with the learning
    models used in models.py
    :return: None
    """
    with open(NN_PICKLE, 'rb') as result:
        grid = pickle.load(result)
        net = pickle.load(result)
        accuracy = pickle.load(result)

    print("A list of named tuples of scores for each set of parameter "
          "combinations in param_grid:")
    print("[parameters, mean_validation_score over CV folds, the list of "
          "scores for each fold]")
    print(grid.grid_scores_)
    print("Estimator that was chosen by the search with the highest score:")
    print(grid.best_estimator_)
    print("Score of best_estimator on the held out data:")
    print(grid.best_score_)
    print("Parameter setting that gave the best results on the held out data:")
    print(grid.best_params_)
    print("Scorer function used on the held out data to choose the best "
          "parameters for the model:")
    print(grid.scorer_)
    print("Accuracy prediction score:")
    print(accuracy)
    grid.save_params_to('/tmp/grid.params')
    net.save_params_to('/tmp/net.params')

    with open(SVM_PICKLE, 'rb') as result:
        mean_abs = pickle.load(result)
        mean_sq = pickle.load(result)
        median_abs = pickle.load(result)
        r2 = pickle.load(result)
        exp_var_score = pickle.load(result)

    print("Mean absolute error regression loss:")
    print mean_abs
    print("Mean squared error regression loss:")
    print mean_sq
    print("Median absolute error regression loss:")
    print median_abs
    print("R^2 (coefficient of determination) regression score function:")
    print r2
    print("Explained variance regression score function:")
    print exp_var_score
<commit_msg>Added file header<commit_after>#!/usr/bin/env python

"""
Tasks performed after model building: Metric analysis and display
"""

import pickle

__author__ = "Pearl Philip"
__credits__ = ["Pearl Philip", "David Beck"]
__license__ = "BSD 3-Clause License"
__maintainer__ = "Pearl Philip"
__email__ = "pphilip@uw.edu"
__status__ = "Production"

NN_PICKLE = 'nn_data.pkl'
SVM_PICKLE = 'svm_data.pkl'

def results():
    """
    Printing results and metrics from the pickles used with the learning
    models used in models.py
    :return: None
    """
    with open(NN_PICKLE, 'rb') as result:
        grid = pickle.load(result)
        net = pickle.load(result)
        accuracy = pickle.load(result)

    print("A list of named tuples of scores for each set of parameter "
          "combinations in param_grid:")
    print("[parameters, mean_validation_score over CV folds, the list of "
          "scores for each fold]")
    print(grid.grid_scores_)
    print("Estimator that was chosen by the search with the highest score:")
    print(grid.best_estimator_)
    print("Score of best_estimator on the held out data:")
    print(grid.best_score_)
    print("Parameter setting that gave the best results on the held out data:")
    print(grid.best_params_)
    print("Scorer function used on the held out data to choose the best "
          "parameters for the model:")
    print(grid.scorer_)
    print("Accuracy prediction score:")
    print(accuracy)
    grid.save_params_to('/tmp/grid.params')
    net.save_params_to('/tmp/net.params')

    with open(SVM_PICKLE, 'rb') as result:
        mean_abs = pickle.load(result)
        mean_sq = pickle.load(result)
        median_abs = pickle.load(result)
        r2 = pickle.load(result)
        exp_var_score = pickle.load(result)

    print("Mean absolute error regression loss:")
    print mean_abs
    print("Mean squared error regression loss:")
    print mean_sq
    print("Median absolute error regression loss:")
    print median_abs
    print("R^2 (coefficient of determination) regression score function:")
    print r2
    print("Explained variance regression score function:")
    print exp_var_score
<|endoftext|>