def validate_config(cls, service_config, target): if ('udd' in service_config and asbool(service_config.get('udd')) and 'email' not in service_config): die("[%s] has no 'bts.email' but UDD search was requested" % (target, )) if 'packages' not in service_config and 'email' not in service_config: die("[%s] has neither 'bts.email' or 'bts.packages'" % (target, )) if ('udd_ignore_sponsor' in service_config and (not asbool(service_config.get('udd')))): die("[%s] defines settings for UDD search without enabling" " UDD search" % (target, )) IssueService.validate_config(service_config, target)
def __init__(self, main_config, main_section, target): self.config = ServiceConfig(self.CONFIG_PREFIX, main_config, target) self.main_section = main_section self.target = target self.desc_len = 35 if main_config.has_option(self.main_section, 'description_length'): self.desc_len = main_config.getint( self.main_section, 'description_length') self.anno_len = 45 if main_config.has_option(self.main_section, 'annotation_length'): self.anno_len = main_config.getint( self.main_section, 'annotation_length') self.inline_links = True if main_config.has_option(self.main_section, 'inline_links'): self.inline_links = asbool(main_config.get( self.main_section, 'inline_links')) self.annotation_links = not self.inline_links if main_config.has_option(self.main_section, 'annotation_links'): self.annotation_links = asbool( main_config.get(self.main_section, 'annotation_links') ) self.annotation_comments = True if main_config.has_option(self.main_section, 'annotation_comments'): self.annotation_comments = asbool( main_config.get(self.main_section, 'annotation_comments') ) self.shorten = False if main_config.has_option(self.main_section, 'shorten'): self.shorten = asbool(main_config.get(self.main_section, 'shorten')) self.add_tags = [] if 'add_tags' in self.config: for raw_option in self.config.get('add_tags').split(','): option = raw_option.strip(' +;') if option: self.add_tags.append(option) self.default_priority = 'M' if 'default_priority' in self.config: self.default_priority = self.config.get('default_priority') log.info("Working on [%s]", self.target)
def validate_config(cls, service_config, target): if ('udd' in service_config and asbool(service_config.get('udd')) and 'email' not in service_config): die("[%s] has no 'bts.email' but UDD search was requested" % (target,)) if 'packages' not in service_config and 'email' not in service_config: die("[%s] has neither 'bts.email' or 'bts.packages'" % (target,)) if ('udd_ignore_sponsor' in service_config and (not asbool(service_config.get('udd')))): die("[%s] defines settings for UDD search without enabling" " UDD search" % (target,)) IssueService.validate_config(service_config, target)
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) self.base_uri = self.config_get('base_uri') self.username = self.config_get('username') self.password = self.config_get('password') self.ignore_cc = self.config_get_default('ignore_cc', default=False, to_type=lambda x: x == "True") # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = asbool(self.config_get_default('advanced', 'no')) if not self.password or self.password.startswith("@oracle:"): self.password = get_service_password( self.get_keyring_service(self.config, self.target), self.username, oracle=self.password, interactive=self.config.interactive) url = 'https://%s/xmlrpc.cgi' % self.base_uri self.bz = bugzilla.Bugzilla(url=url) self.bz.login(self.username, self.password)
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) self.base_uri = self.config_get('base_uri') self.username = self.config_get('username') self.password = self.config_get('password') self.ignore_cc = self.config_get_default('ignore_cc', default=False, to_type=lambda x: x == "True") self.query_url = self.config_get_default('query_url', default=None) self.include_needinfos = self.config_get_default( 'include_needinfos', False, to_type=lambda x: x == "True") self.open_statuses = self.config_get_default( 'open_statuses', _open_statuses, to_type=lambda x: x.split(',')) log.name(self.target).debug(" filtering on statuses: {0}", self.open_statuses) # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = asbool(self.config_get_default('advanced', 'no')) if not self.password or self.password.startswith("@oracle:"): self.password = get_service_password( self.get_keyring_service(self.config, self.target), self.username, oracle=self.password, interactive=self.config.interactive ) url = 'https://%s/xmlrpc.cgi' % self.base_uri self.bz = bugzilla.Bugzilla(url=url) self.bz.login(self.username, self.password)
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) base_uri = self.config.get(self.target, 'bugzilla.base_uri') username = self.config.get(self.target, 'bugzilla.username') password = self.config.get(self.target, 'bugzilla.password') # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = True # Default to True. if self.config.has_option(self.target, 'bugzilla.advanced'): self.advanced = asbool(self.config.get( self.target, 'bugzilla.advanced')) if not password or password.startswith("@oracle:"): service = "bugzilla://%s@%s" % (username, base_uri) password = get_service_password(service, username, oracle=password, interactive=self.config.interactive) url = 'https://%s/xmlrpc.cgi' % base_uri self.bz = bugzilla.Bugzilla(url=url) self.bz.login(username, password)
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) self.base_uri = self.config_get('base_uri') self.username = self.config_get('username') self.ignore_cc = self.config_get_default('ignore_cc', default=False, to_type=lambda x: x == "True") self.query_url = self.config_get_default('query_url', default=None) self.include_needinfos = self.config_get_default( 'include_needinfos', False, to_type=lambda x: x == "True") self.open_statuses = self.config_get_default( 'open_statuses', _open_statuses, to_type=lambda x: x.split(',')) log.name(self.target).debug(" filtering on statuses: {0}", self.open_statuses) # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = asbool(self.config_get_default('advanced', 'no')) self.password = self.config_get_password('password', self.username) url = 'https://%s/xmlrpc.cgi' % self.base_uri self.bz = bugzilla.Bugzilla(url=url) self.bz.login(self.username, self.password)
def aggregate_issues(conf): """ Return all issues from every target. Takes a config object and a callable which returns a shortened url. """ log.name('bugwarrior').info("Starting to aggregate remote issues.") # Create and call service objects for every target in the config targets = [t.strip() for t in conf.get('general', 'targets').split(',')] # This multiprocessing stuff is kind of experimental. use_multiprocessing = conf.has_option('general', 'multiprocessing') and \ asbool(conf.get('general', 'multiprocessing')) if use_multiprocessing: log.name('bugwarrior').info("Spawning %i workers." % len(targets)) pool = multiprocessing.Pool(processes=len(targets)) map_function = pool.map else: log.name('bugwarrior').info("Processing targets in serial.") map_function = map issues_by_target = map_function(_aggregate_issues, zip([conf] * len(targets), targets)) log.name('bugwarrior').info("Done aggregating remote issues.") if WORKER_FAILURE in issues_by_target: log.name('bugwarrior').critical("A worker failed. Aborting.") raise RuntimeError('Worker failure') return sum(issues_by_target, [])
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) base_uri = self.config.get(self.target, 'bugzilla.base_uri') username = self.config.get(self.target, 'bugzilla.username') password = self.config.get(self.target, 'bugzilla.password') # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = True # Default to True. if self.config.has_option(self.target, 'bugzilla.advanced'): self.advanced = asbool( self.config.get(self.target, 'bugzilla.advanced')) if not password or password.startswith("@oracle:"): service = "bugzilla://%s@%s" % (username, base_uri) password = get_service_password( service, username, oracle=password, interactive=self.config.interactive) url = 'https://%s/xmlrpc.cgi' % base_uri self.bz = bugzilla.Bugzilla(url=url) self.bz.login(username, password)
def aggregate_issues(conf): """ Return all issues from every target. Takes a config object and a callable which returns a shortened url. """ log.name('bugwarrior').info("Starting to aggregate remote issues.") # Create and call service objects for every target in the config targets = [t.strip() for t in conf.get('general', 'targets').split(',')] # This multiprocessing stuff is kind of experimental. use_multiprocessing = conf.has_option('general', 'multiprocessing') and \ asbool(conf.get('general', 'multiprocessing')) if use_multiprocessing: log.name('bugwarrior').info("Spawning %i workers." % len(targets)) pool = multiprocessing.Pool(processes=len(targets)) map_function = pool.map else: log.name('bugwarrior').info("Processing targets in serial.") map_function = map issues_by_target = map_function( _aggregate_issues, zip([conf] * len(targets), targets) ) log.name('bugwarrior').info("Done aggregating remote issues.") if WORKER_FAILURE in issues_by_target: log.name('bugwarrior').critical("A worker failed. Aborting.") raise RuntimeError('Worker failure') return sum(issues_by_target, [])
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) self.base_uri = self.config.get('base_uri') self.username = self.config.get('username') self.ignore_cc = self.config.get('ignore_cc', default=False, to_type=lambda x: x == "True") self.query_url = self.config.get('query_url', default=None) self.include_needinfos = self.config.get( 'include_needinfos', False, to_type=lambda x: x == "True") self.open_statuses = self.config.get('open_statuses', _open_statuses, to_type=aslist) log.debug(" filtering on statuses: %r", self.open_statuses) # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = asbool(self.config.get('advanced', 'no')) url = 'https://%s/xmlrpc.cgi' % self.base_uri api_key = self.config.get('api_key', default=None) if api_key: try: self.bz = bugzilla.Bugzilla(url=url, api_key=api_key) except TypeError: raise Exception("Bugzilla API keys require python-bugzilla>=2.1.0") else: password = self.get_password('password', self.username) self.bz = bugzilla.Bugzilla(url=url) self.bz.login(self.username, password)
def __init__(self, config, main_section, target): self.config = config self.main_section = main_section self.target = target self.desc_len = 35 if config.has_option(self.main_section, 'description_length'): self.desc_len = self.config.getint(self.main_section, 'description_length') self.anno_len = 45 if config.has_option(self.main_section, 'annotation_length'): self.anno_len = self.config.getint(self.main_section, 'annotation_length') self.inline_links = True if config.has_option(self.main_section, 'inline_links'): self.inline_links = asbool( config.get(self.main_section, 'inline_links')) self.annotation_links = not self.inline_links if config.has_option(self.main_section, 'annotation_links'): self.annotation_links = asbool( config.get(self.main_section, 'annotation_links')) self.annotation_comments = True if config.has_option(self.main_section, 'annotation_comments'): self.annotation_comments = asbool( config.get(self.main_section, 'annotation_comments')) self.shorten = False if config.has_option(self.main_section, 'shorten'): self.shorten = asbool(config.get(self.main_section, 'shorten')) self.add_tags = [] if config.has_option(self.target, 'add_tags'): for raw_option in self.config.get(self.target, 'add_tags').split(','): option = raw_option.strip(' +;') if option: self.add_tags.append(option) self.default_priority = 'M' if config.has_option(self.target, 'default_priority'): self.default_priority = config.get(self.target, 'default_priority') log.name(target).info("Working on [{0}]", self.target)
def __init__(self, config, main_section, target): self.config = config self.main_section = main_section self.target = target self.desc_len = 35 if config.has_option(self.main_section, 'description_length'): self.desc_len = self.config.getint(self.main_section, 'description_length') self.anno_len = 45 if config.has_option(self.main_section, 'annotation_length'): self.anno_len = self.config.getint(self.main_section, 'annotation_length') self.inline_links = True if config.has_option(self.main_section, 'inline_links'): self.inline_links = asbool(config.get(self.main_section, 'inline_links')) self.annotation_links = not self.inline_links if config.has_option(self.main_section, 'annotation_links'): self.annotation_links = asbool( config.get(self.main_section, 'annotation_links') ) self.annotation_comments = True if config.has_option(self.main_section, 'annotation_comments'): self.annotation_comments = asbool( config.get(self.main_section, 'annotation_comments') ) self.shorten = False if config.has_option(self.main_section, 'shorten'): self.shorten = asbool(config.get(self.main_section, 'shorten')) self.add_tags = [] if config.has_option(self.target, 'add_tags'): for raw_option in self.config.get( self.target, 'add_tags' ).split(','): option = raw_option.strip(' +;') if option: self.add_tags.append(option) self.default_priority = 'M' if config.has_option(self.target, 'default_priority'): self.default_priority = config.get(self.target, 'default_priority') log.name(target).info("Working on [{0}]", self.target)
def aggregate_issues(conf, main_section): """ Return all issues from every target. """ log.name('bugwarrior').info("Starting to aggregate remote issues.") # Create and call service objects for every target in the config targets = [t.strip() for t in conf.get(main_section, 'targets').split(',')] queue = multiprocessing.Queue() log.name('bugwarrior').info("Spawning %i workers." % len(targets)) processes = [] if ( conf.has_option(main_section, 'development') and asbool(conf.get(main_section, 'development')) ): for target in targets: _aggregate_issues( conf, main_section, target, queue, conf.get(target, 'service') ) else: for target in targets: proc = multiprocessing.Process( target=_aggregate_issues, args=(conf, main_section, target, queue, conf.get(target, 'service')) ) proc.start() processes.append(proc) # Sleep for 1 second here to try and avoid a race condition where # all N workers start up and ask the gpg-agent process for # information at the same time. This causes gpg-agent to fumble # and tell some of our workers some incomplete things. time.sleep(1) currently_running = len(targets) while currently_running > 0: issue = queue.get(True) if isinstance(issue, tuple): completion_type, args = issue if completion_type == SERVICE_FINISHED_ERROR: target, e = args log.name('bugwarrior').info("Terminating workers") for process in processes: process.terminate() raise RuntimeError( "critical error in target '{}'".format(target)) currently_running -= 1 continue yield issue log.name('bugwarrior').info("Done aggregating remote issues.")
def aggregate_issues(conf, main_section): """ Return all issues from every target. """ log.name('bugwarrior').info("Starting to aggregate remote issues.") # Create and call service objects for every target in the config targets = [t.strip() for t in conf.get(main_section, 'targets').split(',')] queue = multiprocessing.Queue() log.name('bugwarrior').info("Spawning %i workers." % len(targets)) processes = [] if (conf.has_option(main_section, 'development') and asbool(conf.get(main_section, 'development'))): for target in targets: _aggregate_issues(conf, main_section, target, queue, conf.get(target, 'service')) else: for target in targets: proc = multiprocessing.Process(target=_aggregate_issues, args=(conf, main_section, target, queue, conf.get(target, 'service'))) proc.start() processes.append(proc) # Sleep for 1 second here to try and avoid a race condition where # all N workers start up and ask the gpg-agent process for # information at the same time. This causes gpg-agent to fumble # and tell some of our workers some incomplete things. time.sleep(1) currently_running = len(targets) while currently_running > 0: issue = queue.get(True) if isinstance(issue, tuple): completion_type, args = issue if completion_type == SERVICE_FINISHED_ERROR: target, e = args for process in processes: process.terminate() yield ABORT_PROCESSING, e currently_running -= 1 continue yield issue log.name('bugwarrior').info("Done aggregating remote issues.")
def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) self.base_uri = self.config.get('base_uri') self.username = self.config.get('username') self.ignore_cc = self.config.get('ignore_cc', default=False, to_type=lambda x: x == "True") self.query_url = self.config.get('query_url', default=None) self.include_needinfos = self.config.get('include_needinfos', False, to_type=lambda x: x == "True") self.open_statuses = self.config.get('open_statuses', _open_statuses, to_type=aslist) log.debug(" filtering on statuses: %r", self.open_statuses) # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = asbool(self.config.get('advanced', 'no')) url = 'https://%s/xmlrpc.cgi' % self.base_uri api_key = self.config.get('api_key', default=None) if api_key: try: self.bz = bugzilla.Bugzilla(url=url, api_key=api_key) except TypeError: raise Exception( "Bugzilla API keys require python-bugzilla>=2.1.0") else: password = self.get_password('password', self.username) self.bz = bugzilla.Bugzilla(url=url) self.bz.login(self.username, password)
def send_notification(issue, op, conf): notify_backend = conf.get('notifications', 'backend') # Notifications for growlnotify on Mac OS X if notify_backend == 'growlnotify': import gntp.notifier growl = gntp.notifier.GrowlNotifier( applicationName="Bugwarrior", notifications=["New Updates", "New Messages"], defaultNotifications=["New Messages"], ) growl.register() if op == 'bw_finished': growl.notify( noteType="New Messages", title="Bugwarrior", description="Finished querying for new issues.\n%s" % issue['description'], sticky=asbool(conf.get( 'notifications', 'finished_querying_sticky', 'True')), icon="https://upload.wikimedia.org/wikipedia/" "en/5/59/Taskwarrior_logo.png", priority=1, ) return message = "%s task: %s" % (op, issue['description'].encode("utf-8")) metadata = _get_metadata(issue) if metadata is not None: message += metadata growl.notify( noteType="New Messages", title="Bugwarrior", description=message, sticky=asbool(conf.get( 'notifications', 'task_crud_sticky', 'True')), icon="https://upload.wikimedia.org/wikipedia/" "en/5/59/Taskwarrior_logo.png", priority=1, ) return elif notify_backend == 'pynotify': _cache_logo() import pynotify pynotify.init("bugwarrior") if op == 'bw finished': message = "Finished querying for new issues.\n%s" %\ issue['description'] else: message = "%s task: %s" % ( op, issue['description'].encode("utf-8")) metadata = _get_metadata(issue) if metadata is not None: message += metadata pynotify.Notification("Bugwarrior", message, logo_path).show() elif notify_backend == 'gobject': _cache_logo() from gi.repository import Notify Notify.init("bugwarrior") if op == 'bw finished': message = "Finished querying for new issues.\n%s" %\ issue['description'] else: message = "%s task: %s" % ( op, issue['description'].encode("utf-8")) metadata = _get_metadata(issue) if metadata is not None: message += metadata Notify.Notification.new("Bugwarrior", message, logo_path).show()
def _bool_option(section, option, default): try: return section in conf.sections() and \ asbool(conf.get(section, option, default)) except NoOptionError: return default
def _bool_option(section, option, default): try: return asbool(conf.get(section, option)) except (NoSectionError, NoOptionError): return default
def to_taskwarrior(self): author = self.record['author'] milestone = self.record.get('milestone') created = self.record['created_at'] updated = self.record.get('updated_at') state = self.record['state'] upvotes = self.record.get('upvotes', 0) downvotes = self.record.get('downvotes', 0) work_in_progress = int(asbool(self.record.get('work_in_progress', 0))) assignee = self.record.get('assignee') duedate = self.record.get('due_date') weight = self.record.get('weight') number = (self.record['id'] if self.extra['type'] == 'todo' else self.record['iid']) priority = (self.origin['default_priority'] if self.extra['type'] == 'issue' else 'H') title = ('Todo from %s for %s' % (author['name'], self.extra['project']) if self.extra['type'] == 'todo' else self.record['title']) description = (self.record['body'] if self.extra['type'] == 'todo' else self.record['description']) if milestone and (self.extra['type'] == 'issue' or (self.extra['type'] == 'merge_request' and duedate is None)): duedate = milestone['due_date'] if milestone: milestone = milestone['title'] if created: created = self.parse_date(created).replace(microsecond=0) if updated: updated = self.parse_date(updated).replace(microsecond=0) if duedate: duedate = self.parse_date(duedate) if author: author = author['username'] if assignee: assignee = assignee['username'] self.title = title return { 'project': self.extra['project'], 'priority': priority, 'annotations': self.extra.get('annotations', []), 'tags': self.get_tags(), 'due': duedate, 'entry': created, self.URL: self.extra['issue_url'], self.REPO: self.extra['project'], self.TYPE: self.extra['type'], self.TITLE: title, self.DESCRIPTION: description, self.MILESTONE: milestone, self.NUMBER: str(number), self.CREATED_AT: created, self.UPDATED_AT: updated, self.DUEDATE: duedate, self.STATE: state, self.UPVOTES: upvotes, self.DOWNVOTES: downvotes, self.WORK_IN_PROGRESS: work_in_progress, self.AUTHOR: author, self.ASSIGNEE: assignee, self.NAMESPACE: self.extra['namespace'], self.WEIGHT: weight, }
def to_taskwarrior(self): author = self.record['author'] milestone = self.record.get('milestone') created = self.record['created_at'] updated = self.record.get('updated_at') state = self.record['state'] upvotes = self.record.get('upvotes', 0) downvotes = self.record.get('downvotes', 0) work_in_progress = int(asbool(self.record.get('work_in_progress', 0))) assignee = self.record.get('assignee') duedate = self.record.get('due_date') weight = self.record.get('weight') number = ( self.record['id'] if self.extra['type'] == 'todo' else self.record['iid']) priority = ( self.origin['default_priority'] if self.extra['type'] == 'issue' else 'H') title = ( 'Todo from %s for %s' % (author['name'], self.extra['project']) if self.extra['type'] == 'todo' else self.record['title']) description = ( self.record['body'] if self.extra['type'] == 'todo' else self.record['description']) if milestone and ( self.extra['type'] == 'issue' or (self.extra['type'] == 'merge_request' and duedate is None)): duedate = milestone['due_date'] if milestone: milestone = milestone['title'] if created: created = self.parse_date(created).replace(microsecond=0) if updated: updated = self.parse_date(updated).replace(microsecond=0) if duedate: duedate = self.parse_date(duedate) if author: author = author['username'] if assignee: assignee = assignee['username'] self.title = title return { 'project': self.extra['project'], 'priority': priority, 'annotations': self.extra.get('annotations', []), 'tags': self.get_tags(), 'due': duedate, 'entry': created, self.URL: self.extra['issue_url'], self.REPO: self.extra['project'], self.TYPE: self.extra['type'], self.TITLE: title, self.DESCRIPTION: description, self.MILESTONE: milestone, self.NUMBER: number, self.CREATED_AT: created, self.UPDATED_AT: updated, self.DUEDATE: duedate, self.STATE: state, self.UPVOTES: upvotes, self.DOWNVOTES: downvotes, self.WORK_IN_PROGRESS: work_in_progress, self.AUTHOR: author, self.ASSIGNEE: assignee, self.NAMESPACE: self.extra['namespace'], self.WEIGHT: weight, }
def send_notification(issue, op, conf): notify_backend = conf.get('notifications', 'backend') # Notifications for growlnotify on Mac OS X if notify_backend == 'growlnotify': import gntp.notifier growl = gntp.notifier.GrowlNotifier( applicationName="Bugwarrior", notifications=["New Updates", "New Messages"], defaultNotifications=["New Messages"], ) growl.register() if op == 'bw_finished': growl.notify( noteType="New Messages", title="Bugwarrior", description="Finished querying for new issues.\n%s" % issue['description'], sticky=asbool(conf.get( 'notifications', 'finished_querying_sticky', 'True')), icon="https://upload.wikimedia.org/wikipedia/" "en/5/59/Taskwarrior_logo.png", priority=1, ) return message = "%s task: %s" % (op, issue['description'].encode("utf-8")) metadata = _get_metadata(issue) if metadata is not None: message += metadata growl.notify( noteType="New Messages", title="Bugwarrior", description=message, sticky=asbool(conf.get( 'notifications', 'task_crud_sticky', 'True')), icon="https://upload.wikimedia.org/wikipedia/" "en/5/59/Taskwarrior_logo.png", priority=1, ) return elif notify_backend == 'pynotify': _cache_logo() import pynotify pynotify.init("bugwarrior") if op == 'bw finished': message = "Finished querying for new issues.\n%s" %\ issue['description'] else: message = "%s task: %s" % ( op, issue['description'].encode("utf-8")) metadata = _get_metadata(issue) if metadata is not None: message += metadata pynotify.Notification("Bugwarrior", message, logo_path).show() elif notify_backend == 'gobject': _cache_logo() from gi.repository import Notify Notify.init("bugwarrior") if op == 'bw finished': message = "Finished querying for new issues.\n%s" %\ issue['description'] else: message = "%s task: %s" % ( op, issue['description'].encode("utf-8")) metadata = _get_metadata(issue) if metadata is not None: message += metadata.encode("utf-8") Notify.Notification.new("Bugwarrior", message, logo_path).show()