From 73ffa6c6f1e648653099a31175a25836d8acd140 Mon Sep 17 00:00:00 2001 From: Christophe Benz Date: Wed, 28 Apr 2010 17:39:59 +0200 Subject: [PATCH] Results API --- weboob/backends/bnporc/browser.py | 2 - weboob/frontends/videoob/application.py | 81 ++++++++------- weboob/ouiboube.py | 2 +- weboob/tools/application/base.py | 9 ++ weboob/tools/application/console.py | 98 +++++------------- weboob/tools/application/results.py | 126 ++++++++++++++++++++++++ 6 files changed, 203 insertions(+), 115 deletions(-) create mode 100644 weboob/tools/application/results.py diff --git a/weboob/backends/bnporc/browser.py b/weboob/backends/bnporc/browser.py index ccb40914..4fe1e8cf 100644 --- a/weboob/backends/bnporc/browser.py +++ b/weboob/backends/bnporc/browser.py @@ -18,8 +18,6 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ -from cStringIO import StringIO - from weboob.tools.browser import BaseBrowser, BrowserIncorrectPassword from weboob.backends.bnporc import pages diff --git a/weboob/frontends/videoob/application.py b/weboob/frontends/videoob/application.py index b0f73f4f..e563170a 100644 --- a/weboob/frontends/videoob/application.py +++ b/weboob/frontends/videoob/application.py @@ -20,6 +20,45 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. from weboob.capabilities.video import ICapVideoProvider from weboob.tools.application import ConsoleApplication +from weboob.tools.application.results import BaseItem, FieldException, ObjectItem + + +class VideoItem(ObjectItem): + def format(self, select=[]): + if select: + return u'\t'.join(self.get(field) for field in select) + else: + lines = [ + u'ID: %s' % self.obj.id, + u'title: %s'% self.obj.title, + u'duration: %s'% self.obj.formatted_duration, + u'URL: %s'% self.obj.url, + u'author: %s'% self.obj.author, + u'date: %s'% self.obj.date, + u'rating_max: %s'% self.obj.rating, + u'rating: %s'% self.obj.rating_max, + ] + return u'\n'.join(lines) + + +class SearchResultItem(BaseItem): + def __init__(self, _id, title, duration): + self.id = _id + self.title = title + self.duration = duration + + def get(self, name): + try: + return getattr(self, name) + except AttributeError: + raise FieldException(name) + + def format(self, select=[]): + if select: + return u'\t'.join(unicode(self.get(field)) for field in select) + else: + return u'%s %s (%s)' % (self.id, self.title, self.duration) + class Videoob(ConsoleApplication): APPNAME = 'videoob' @@ -37,44 +76,14 @@ class Videoob(ConsoleApplication): @ConsoleApplication.command('Get video information (accept ID or URL)') def command_info(self, id): - results = {} - for backend in self.weboob.iter_backends(): - video = backend.get_video(id) + for backend, video in self.weboob.do('get_video', id): if video is None: continue - rows = [] - rows.append(('ID', video.id)) - if video.title: - rows.append(('Video title', video.title)) - if video.duration: - rows.append(('Duration', '%d:%02d:%02d' % ( - video.duration / 3600, (video.duration % 3600) / 60, video.duration % 60))) - if video.url: - rows.append(('URL', video.url)) - if video.author: - rows.append(('Author', video.author)) - if video.date: - rows.append(('Date', video.date)) - if video.rating_max: - rows.append(('Rating', '%s / %s' % (video.rating, video.rating_max))) - elif video.rating: - rows.append(('Rating', video.rating)) - results[backend.name] = rows - return results + print self.format(VideoItem(video)) @ConsoleApplication.command('Search videos') def command_search(self, pattern=None): - results = {} - if pattern: - results['BEFORE'] = u'Search pattern: %s' % pattern - else: - results['BEFORE'] = u'Last videos' - results['HEADER'] = ('ID', 'Title', 'Duration') - - for backend, video in self.weboob.do('iter_search_results', pattern=pattern, nsfw=self.options.nsfw): - row = (video.id, video.title, video.formatted_duration) - try: - results[backend.name].append(row) - except KeyError: - results[backend.name] = [row] - return results + print u'Search pattern: %s' % pattern if pattern else u'Last videos' + for backend, video in self.weboob.do( + 'iter_search_results', pattern=pattern, nsfw=self.options.nsfw): + print self.format(SearchResultItem(video.id, title=video.title, duration=video.formatted_duration)) diff --git a/weboob/ouiboube.py b/weboob/ouiboube.py index c80beb10..26ec710b 100644 --- a/weboob/ouiboube.py +++ b/weboob/ouiboube.py @@ -101,7 +101,7 @@ class Weboob(object): @param caps Optional list of capabilities to select backends @return iterator on selected backends. """ - for name, backend in self.backends.iteritems(): + for name, backend in sorted(self.backends.iteritems()): if caps is None or backend.has_caps(caps): with backend: yield backend diff --git a/weboob/tools/application/base.py b/weboob/tools/application/base.py index 54fb6d8b..f4c12948 100644 --- a/weboob/tools/application/base.py +++ b/weboob/tools/application/base.py @@ -131,6 +131,12 @@ class BaseApplication(object): """ return set() + def _handle_app_options(self): + """ + Overload this method in subclasses if you want to handle options defined in subclass constructor. + """ + pass + @classmethod def run(klass, args=None): if args is None: @@ -158,6 +164,9 @@ class BaseApplication(object): log_format = '%(asctime)s:%(levelname)s:%(filename)s:%(lineno)d:%(funcName)s %(message)s' logging.basicConfig(stream=sys.stdout, level=level, format=log_format) app._enabled_backends = app.options.backends.split(',') if app.options.backends else None + + app._handle_app_options() + try: sys.exit(app.main(args)) except KeyboardInterrupt: diff --git a/weboob/tools/application/console.py b/weboob/tools/application/console.py index 6014c112..9870e800 100644 --- a/weboob/tools/application/console.py +++ b/weboob/tools/application/console.py @@ -27,70 +27,12 @@ from functools import partial from weboob.modules import BackendsConfig from .base import BaseApplication +from .results import FieldException, ItemGroup __all__ = ['ConsoleApplication'] -class TableFormatter(object): - @classmethod - def format(klass, data): - from prettytable import PrettyTable - formatted = u'' - before = data.get('BEFORE') - if before is not None: - formatted += u'%s\n' % before - del data['BEFORE'] - header = data.get('HEADER') - if header is not None: - del data['HEADER'] - for backend_name, result in data.iteritems(): - if not result: - continue - if header is None: - header = ['' for e in xrange(len(result[0]))] - table = PrettyTable(list(header)) - for col in header: - table.set_field_align(col, 'l') - for row in result: - table.add_row(row) - formatted += u'%s\n%s\n' % (backend_name, unicode(table)) - return unicode(formatted).strip() - - -class TextFormatter(object): - @classmethod - def format(klass, data): - formatted = u'' - before = data.get('BEFORE') - if before is not None: - formatted += u'%s\n' % before - del data['BEFORE'] - header = data.get('HEADER') - if header is not None: - del data['HEADER'] - for backend_name, result in data.iteritems(): - if not result: - continue - if header is None: - header = ['' for e in xrange(len(result[0]))] - formatted += u'%s\n%s\n' % (backend_name, '=' * len(backend_name)) - for row in result: - formatted_cols = [] - for i, col in enumerate(row): - if header[i]: - formatted_cols.append(u'%s: %s' % (header[i], col)) - else: - formatted_cols.append(unicode(col)) - formatted += u'%s\n' % u' '.join(formatted_cols) - return unicode(formatted).strip() - - -formatters = {'text': TextFormatter, - 'table': TableFormatter, - } - - class ConsoleApplication(BaseApplication): SYNOPSIS = 'Usage: %prog [options (-h for help)] command [parameters...]' @@ -100,7 +42,6 @@ class ConsoleApplication(BaseApplication): except BackendsConfig.WrongPermissions, e: print >>sys.stderr, 'Error: %s' % e sys.exit(1) - self.default_output_format = None self._parser.format_description = lambda x: self._parser.description @@ -111,8 +52,13 @@ class ConsoleApplication(BaseApplication): command = '%s %s' % (name, arguments) self._parser.description += ' %-30s %s\n' % (command, doc_string) - self._parser.add_option('-o', '--output-format', choices=formatters.keys(), - help='output format %s (default: table)' % formatters.keys()) + self._parser.add_option('-s', '--select', help='select result item key(s) to display (comma-separated)') + + def _handle_app_options(self): + if self.options.select: + self.selected_fields = self.options.select.split(',') + else: + self.selected_fields = None def _get_completions(self): return set(name for name, arguments, doc_string in self._commands) @@ -188,28 +134,25 @@ class ConsoleApplication(BaseApplication): sys.stderr.write("Command '%s' takes %d arguments.\n" % (command, nb_min_args)) return 1 - command_result = func(*args) + try: + command_result = func(*args) + except FieldException, e: + logging.error(e) + return 1 # Process result - if isinstance(command_result, dict): - if self.options.output_format is not None: - output_format = self.options.output_format - else: - if self.default_output_format is not None: - output_format = self.default_output_format - else: - output_format = 'table' - try: - print formatters[output_format].format(command_result) - except ImportError, e: - logging.error(u'Could not use formatter "%s". Error: %s' % (output_format, e)) + if isinstance(command_result, ItemGroup): + print command_result.format(select=self.selected_fields) + return 0 + elif isinstance(command_result, (str, unicode)): + print command_result return 0 elif isinstance(command_result, int): return command_result elif command_result is None: return 0 else: - raise Exception('Should never go here') + raise Exception('command_result type not expected: %s' % type(command_result)) _commands = [] def register_command(f, doc_string, register_to=_commands): @@ -238,5 +181,8 @@ class ConsoleApplication(BaseApplication): def command(doc_string, f=register_command): return partial(f, doc_string=doc_string) + def format(self, result): + return result.format(select=self.selected_fields) + register_command = staticmethod(register_command) command = staticmethod(command) diff --git a/weboob/tools/application/results.py b/weboob/tools/application/results.py new file mode 100644 index 00000000..6e0fffd1 --- /dev/null +++ b/weboob/tools/application/results.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010 Christophe Benz +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +__all__ = ['BaseItem', 'FieldException', 'FieldsItem', 'ItemGroup', 'ObjectItem'] + + +class FieldException(Exception): + def __init__(self, name): + Exception.__init__(self, 'Field "%s" does not exist.' % name) + + +class ItemGroup(object): + def __init__(self, name=u'', header=None): + self.name = name + self._groups = [] + self._items = [] + self.header = header + + def add_item(self, item): + self._items.append(item) + + def add_items(self, items): + self._items.extend(items) + + def iter_items(self): + return iter(self._items) + + def deep_iter_items(self): + for i in self._items: + yield i + for g in self._groups: + for i in g.iter_items(): + yield i + + def add_group(self, group): + self._groups.append(group) + + def get_group(self, name): + l = [group for group in self._groups if group.name == name] + if l: + return l[0] + else: + return None + + def get_or_create_group(self, name, group_class=None): + if group_class is None: + group_class = ItemGroup + group = self.get_group(name) + if group: + return group + else: + new_group = group_class(name) + self.add_group(new_group) + return new_group + + def iter_groups(self): + return iter(self._groups) + + def format(self, select=[]): + formatted = u'' + if select: + formatted += u'\n'.join(item.format(select) for item in self.deep_iter_items()) + else: + if self.header: + formatted += '%s\n' % self.header + formatted += u'\n'.join(item.format() for item in self.iter_items()) + formatted += u'\n\n'.join(group.format() for group in self.iter_groups()) + return formatted + + +class BaseItem(object): + def get(self, name): + raise NotImplementedError() + + def format(self, select=[]): + raise NotImplementedError() + + +class FieldsItem(BaseItem): + def __init__(self, fields=[]): + self._fields = fields + + def add_field(self, *args, **kwargs): + if args: + name, value = args + elif kwargs: + name, value = kwargs.items()[0] + self._fields.append((name, value)) + + def get(self, name): + try: + return [value for _name, value in self._fields if name.lower() == _name.lower()][0] + except IndexError: + raise FieldException(name) + + def format(self, select=[]): + if select: + return [value for name, value in self._fields if name in select] + else: + return u'; '.join(u'%s: %s' % (name, value) for name, value in self._fields) + + +class ObjectItem(BaseItem): + def __init__(self, obj): + self.obj = obj + + def get(self, name): + try: + return getattr(self.obj, name) + except AttributeError: + raise FieldException(name)