diff --git a/weboob/applications/boobank/boobank.py b/weboob/applications/boobank/boobank.py index 516e9131..7c0b3073 100644 --- a/weboob/applications/boobank/boobank.py +++ b/weboob/applications/boobank/boobank.py @@ -35,14 +35,14 @@ class Boobank(ConsoleApplication): COPYRIGHT = 'Copyright(C) 2010 Romain Bignon' def main(self, argv): - self.load_configured_backends(ICapBank) return self.process_command(*argv[1:]) @ConsoleApplication.command('List every available accounts') def command_list(self): + self.load_configured_backends(ICapBank) try: for backend, account in self.do('iter_accounts'): - self.format(account, backend.name) + self.format(account) except weboob.core.CallErrors, errors: for backend, error, backtrace in errors: if isinstance(error, weboob.tools.browser.BrowserIncorrectPassword): @@ -52,19 +52,13 @@ class Boobank(ConsoleApplication): @ConsoleApplication.command('Display all future operations') def command_coming(self, id): - total = 0.0 + id, backend_name = self.parse_id(id) + names = (backend_name,) if backend_name is not None else None + self.load_configured_backends(ICapBank, names=names) def do(backend): account = backend.get_account(id) return backend.iter_operations(account) - try: - for backend, operation in self.do(do): - self.format(operation, backend.name) - total += operation.amount - except weboob.core.CallErrors, errors: - for backend, error, backtrace in errors: - if isinstance(error, AccountNotFound): - logging.error(u'Error: account %s not found' % id) - else: - logging.error(u'Error[%s]: %s\n%s' % (backend.name, error, backtrace)) + for backend, operation in self.do(do): + self.format(operation) diff --git a/weboob/applications/chatoob/chatoob.py b/weboob/applications/chatoob/chatoob.py index 221ef2d5..e7e79601 100644 --- a/weboob/applications/chatoob/chatoob.py +++ b/weboob/applications/chatoob/chatoob.py @@ -47,12 +47,12 @@ class Chatoob(ConsoleApplication): @ConsoleApplication.command('list online contacts') def command_list(self): for backend, contact in self.do('iter_contacts', status=Contact.STATUS_ONLINE, caps=ICapContact): - self.format(contact, backend.name) + self.format(contact) @ConsoleApplication.command('get messages') def command_messages(self): for backend, message in self.do('iter_chat_messages'): - self.format(message, backend.name) + self.format(message) @ConsoleApplication.command('send message to contact') def command_send(self, _id, message): diff --git a/weboob/applications/geolooc/geolooc.py b/weboob/applications/geolooc/geolooc.py index 275b846b..048c95b9 100644 --- a/weboob/applications/geolooc/geolooc.py +++ b/weboob/applications/geolooc/geolooc.py @@ -37,6 +37,6 @@ class Geolooc(ConsoleApplication): self.load_configured_backends(ICapGeolocIp) for backend, location in self.do('get_location', argv[1]): - self.format(location, backend.name) + self.format(location) return 0 diff --git a/weboob/applications/traveloob/traveloob.py b/weboob/applications/traveloob/traveloob.py index b5b6044f..552265e6 100644 --- a/weboob/applications/traveloob/traveloob.py +++ b/weboob/applications/traveloob/traveloob.py @@ -37,7 +37,7 @@ class Traveloob(ConsoleApplication): def command_stations(self, pattern): self.load_backends(ICapTravel) for backend, station in self.do('iter_station_search', pattern): - self.format(station, backend.name) + self.format(station) @ConsoleApplication.command('List all departures for a given station') def command_departures(self, station, arrival=None): @@ -59,4 +59,4 @@ class Traveloob(ConsoleApplication): self.load_backends(ICapTravel, names=backends) for backend, departure in self.do('iter_station_departures', station_id, arrival_id): - self.format(departure, backend.name) + self.format(departure) diff --git a/weboob/applications/videoob/videoob.py b/weboob/applications/videoob/videoob.py index 081cd3aa..e3fe0489 100644 --- a/weboob/applications/videoob/videoob.py +++ b/weboob/applications/videoob/videoob.py @@ -45,7 +45,7 @@ class Videoob(ConsoleApplication): for backend, video in self.do('get_video', _id): if video is None: continue - self.format(video, backend.name) + self.format(video) @ConsoleApplication.command('Search for videos') def command_search(self, pattern=None): @@ -53,4 +53,4 @@ class Videoob(ConsoleApplication): self.set_formatter_header(u'Search pattern: %s' % pattern if pattern else u'Latest videos') for backend, video in self.do('iter_search_results', pattern=pattern, nsfw=self.options.nsfw, max_results=self.options.count): - self.format(video, backend.name) + self.format(video) diff --git a/weboob/applications/weboobcli/weboobcli.py b/weboob/applications/weboobcli/weboobcli.py index a8d2f11c..6055344e 100644 --- a/weboob/applications/weboobcli/weboobcli.py +++ b/weboob/applications/weboobcli/weboobcli.py @@ -47,6 +47,6 @@ class WeboobCli(ConsoleApplication): self.load_backends(cap_s) for backend, obj in self.do(cmd, *args): - self.format(obj, backend.name) + self.format(obj) return 0 diff --git a/weboob/applications/weboorrents/weboorrents.py b/weboob/applications/weboorrents/weboorrents.py index 1307fa73..18410dc5 100644 --- a/weboob/applications/weboorrents/weboorrents.py +++ b/weboob/applications/weboorrents/weboorrents.py @@ -42,7 +42,7 @@ class Weboorrents(ConsoleApplication): found = 0 for backend, torrent in self.do('get_torrent', _id, backends=backend_name): if torrent: - self.format(torrent, backend.name) + self.format(torrent) found = 1 if not found: @@ -67,4 +67,4 @@ class Weboorrents(ConsoleApplication): def command_search(self, pattern=None): self.set_formatter_header(u'Search pattern: %s' % pattern if pattern else u'Latest torrents') for backend, torrent in self.do('iter_torrents', pattern=pattern): - self.format(torrent, backend.name) + self.format(torrent) diff --git a/weboob/applications/wetboobs/wetboobs.py b/weboob/applications/wetboobs/wetboobs.py index 431356f1..f75ad6d1 100644 --- a/weboob/applications/wetboobs/wetboobs.py +++ b/weboob/applications/wetboobs/wetboobs.py @@ -39,13 +39,13 @@ class WetBoobs(ConsoleApplication): @ConsoleApplication.command('search cities') def command_search(self, pattern): for backend, city in self.do('iter_city_search', pattern): - self.format(city, backend.name) + self.format(city) @ConsoleApplication.command('get current weather') def command_current(self, city): try: for backend, current in self.do('get_current', city): - self.format(current, backend.name) + self.format(current) except CallErrors, e: for error in e: if isinstance(error, CityNotFound): @@ -57,7 +57,7 @@ class WetBoobs(ConsoleApplication): def command_forecasts(self, city): try: for backend, forecast in self.do('iter_forecast', city): - self.format(forecast, backend.name) + self.format(forecast) except CallErrors, e: for error in e: if isinstance(error, CityNotFound): diff --git a/weboob/capabilities/bank.py b/weboob/capabilities/bank.py index ecae03a8..153ce385 100644 --- a/weboob/capabilities/bank.py +++ b/weboob/capabilities/bank.py @@ -21,7 +21,7 @@ if sys.version_info[:2] <= (2, 5): from weboob.tools.property import property -from .base import IBaseCap +from .base import IBaseCap, CapBaseObject __all__ = ['Account', 'AccountNotFound', 'ICapBank', 'Operation'] @@ -31,9 +31,10 @@ class AccountNotFound(Exception): pass -class Account(object): +class Account(CapBaseObject): + FIELDS = ('label', 'balance', 'coming') def __init__(self): - self.id = 0 + CapBaseObject.__init__(self, 0) self.label = '' self._balance = 0.0 self._coming = 0.0 @@ -59,8 +60,10 @@ class Account(object): return u"" % (self.id, self.label) -class Operation(object): +class Operation(CapBaseObject): + FIELDS = ('date', 'label', 'amount') def __init__(self): + CapBaseObject(self, 0) self.date = None self._label = u'' self._amount = 0.0 diff --git a/weboob/capabilities/base.py b/weboob/capabilities/base.py index 51c85b57..40dea59a 100644 --- a/weboob/capabilities/base.py +++ b/weboob/capabilities/base.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright(C) 2010 Christophe Benz +# Copyright(C) 2010 Christophe Benz, Romain Bignon # # 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 @@ -16,7 +16,9 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -__all__ = ['IBaseCap', 'NotLoaded', 'LoadingError'] +from weboob.tools.misc import iter_fields + +__all__ = ['IBaseCap', 'NotLoaded', 'LoadingError', 'CapBaseObject'] class NotLoadedMeta(type): @@ -45,3 +47,20 @@ class LoadingError(object): class IBaseCap(object): pass + +class CapBaseObject(object): + FIELDS = None + + def __init__(self, id, backend=None): + self.id = id + self.backend = backend + + def iter_fields(self): + if self.FIELDS is None: + for key, value in iter_fields(self): + if key != 'backend': + yield key, value + else: + yield 'id', self.id + for attrstr in self.FIELDS: + yield attrstr, getattr(self, attrstr) diff --git a/weboob/capabilities/chat.py b/weboob/capabilities/chat.py index b40e9de9..60613e40 100644 --- a/weboob/capabilities/chat.py +++ b/weboob/capabilities/chat.py @@ -18,7 +18,7 @@ import datetime -from .base import IBaseCap +from .base import IBaseCap, CapBaseObject __all__ = ['ChatException', 'ICapChat'] @@ -28,8 +28,11 @@ class ChatException(Exception): pass -class ChatMessage(object): - def __init__(self, id_from, id_to, message, date=None): +class ChatMessage(CapBaseObject): + FIELDS = ('id_from', 'id_to', 'date', 'message') + + def __init__(self, id_from, id_to, message, date=None): + CapBaseObject.__init__(self, '%s.%s' % (id_from, id_to)) self.id_from = id_from self.id_to = id_to self.message = message diff --git a/weboob/capabilities/contact.py b/weboob/capabilities/contact.py index 8afd8a6d..44aea9f4 100644 --- a/weboob/capabilities/contact.py +++ b/weboob/capabilities/contact.py @@ -16,7 +16,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from .base import IBaseCap +from .base import IBaseCap, CapBaseObject from weboob.tools.ordereddict import OrderedDict @@ -50,14 +50,16 @@ class ContactPhoto(object): def __repr__(self): return u'' % (self.name, len(self.data), len(self.thumbnail_data)) -class Contact(object): +class Contact(CapBaseObject): + FIELDS = ('name', 'status', 'status_msg', 'summary', 'avatar', 'photos', 'profile') + STATUS_ONLINE = 0x001 STATUS_AWAY = 0x002 STATUS_OFFLINE = 0x004 STATUS_ALL = 0xfff def __init__(self, id, name, status): - self.id = id + CapBaseObject.__init__(self, id) self.name = name self.status = status self.status_msg = u'' @@ -74,17 +76,6 @@ class Contact(object): for key, value in kwargs.iteritems(): setattr(photo, key, value) - def iter_fields(self): - return {'id': self.id, - 'name': self.name, - 'status': self.status, - 'status_msg': self.status_msg, - 'summary': self.summary, - 'avatar': self.avatar, - 'photos': self.photos, - 'profile': self.profile, - }.iteritems() - class ICapContact(IBaseCap): def iter_contacts(self, status=Contact.STATUS_ALL, ids=None): """ diff --git a/weboob/capabilities/geolocip.py b/weboob/capabilities/geolocip.py index caefb156..e8fb042c 100644 --- a/weboob/capabilities/geolocip.py +++ b/weboob/capabilities/geolocip.py @@ -16,13 +16,16 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from .cap import ICap +from .base import IBaseCap, CapBaseObject __all__ = ('IpLocation', 'ICapGeolocIp') -class IpLocation(object): +class IpLocation(CapBaseObject): + FIELDS = ('city', 'region', 'zipcode', 'country', 'lt', 'lg', 'host', 'tls', 'isp') def __init__(self, ipaddr): + CapBaseObject.__init__(self, ipaddr) + self.ipaddr = ipaddr self.city = None self.region = None @@ -34,6 +37,6 @@ class IpLocation(object): self.tld = None self.isp = None -class ICapGeolocIp(ICap): +class ICapGeolocIp(IBaseCap): def get_location(self, ipaddr): raise NotImplementedError() diff --git a/weboob/capabilities/torrent.py b/weboob/capabilities/torrent.py index 3b87a408..754117db 100644 --- a/weboob/capabilities/torrent.py +++ b/weboob/capabilities/torrent.py @@ -16,15 +16,19 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from .base import IBaseCap +from .base import IBaseCap, CapBaseObject, NotLoaded __all__ = ['ICapTorrent', 'Torrent'] -class Torrent(object): - def __init__(self, id, name, date=None, size=0.0, url=u'', seeders=0, leechers=0, files=[], description=u''): - self.id = id +class Torrent(CapBaseObject): + FIELDS = ('name', 'size', 'date', 'url', 'seeders', 'leechers', 'files', 'description') + + def __init__(self, id, name, date=NotLoaded, size=NotLoaded, url=NotLoaded, + seeders=NotLoaded, leechers=NotLoaded, files=NotLoaded, + description=NotLoaded): + CapBaseObject.__init__(self, id) self.name = name self.date = date self.size = size diff --git a/weboob/capabilities/travel.py b/weboob/capabilities/travel.py index 0222ee93..0403308c 100644 --- a/weboob/capabilities/travel.py +++ b/weboob/capabilities/travel.py @@ -18,7 +18,7 @@ from datetime import time -from .base import IBaseCap +from .base import IBaseCap, CapBaseObject __all__ = ['Departure', 'ICapTravel', 'Station'] @@ -45,18 +45,23 @@ class ICapTravel(IBaseCap): raise NotImplementedError() -class Station(object): - def __init__(self, _id, name): - self.id = _id +class Station(CapBaseObject): + FIELDS = ('name',) + + def __init__(self, id, name): + CapBaseObject.__init__(self, id) self.name = name def __repr__(self): return "" % (self.id, self.name) -class Departure(object): - def __init__(self, _id, _type, _time): - self.id = _id +class Departure(CapBaseObject): + FIELDS = ('type', 'time', 'departure_station', 'arrival_station', 'late', 'information', 'plateform') + + def __init__(self, id, _type, _time): + CapBaseObject.__init__(self, id) + self.type = _type self.time = _time self.departure_station = u'' diff --git a/weboob/capabilities/video.py b/weboob/capabilities/video.py index 196faeaf..c6c056e1 100644 --- a/weboob/capabilities/video.py +++ b/weboob/capabilities/video.py @@ -16,7 +16,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from .base import IBaseCap, LoadingError, NotLoaded +from .base import IBaseCap, LoadingError, NotLoaded, CapBaseObject __all__ = ['BaseVideo', 'ICapVideo'] @@ -37,7 +37,9 @@ class VideoThumbnail(object): return self.data is not NotLoaded -class BaseVideo(object): +class BaseVideo(CapBaseObject): + FIELDS = ('title', 'url', 'author', 'duration', 'date', 'rating', 'rating_max', 'thumbnail', 'nsfw') + def __init__(self, _id, title=NotLoaded, url=NotLoaded, author=NotLoaded, duration=NotLoaded, date=NotLoaded, rating=NotLoaded, rating_max=NotLoaded, thumbnail=NotLoaded, thumbnail_url=None, nsfw=False): self.id = unicode(_id) diff --git a/weboob/capabilities/weather.py b/weboob/capabilities/weather.py index 01c7e28d..860e6097 100644 --- a/weboob/capabilities/weather.py +++ b/weboob/capabilities/weather.py @@ -16,14 +16,15 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -from .base import IBaseCap +from .base import IBaseCap, CapBaseObject __all__ = ['City', 'CityNotFound', 'Current', 'Forecast', 'ICapWeather'] -class Forecast(object): +class Forecast(CapBaseObject): def __init__(self, date, low, high, text, unit): + CapBaseObject.__init__(self, date) self.date = date self.low = low self.high = high @@ -31,17 +32,18 @@ class Forecast(object): self.unit = unit -class Current(object): +class Current(CapBaseObject): def __init__(self, date, temp, text, unit): + CapBaseObject.__init__(self, date) self.date = date self.temp = temp self.text = text self.unit = unit -class City(object): - def __init__(self, city_id, name): - self.city_id = city_id +class City(CapBaseObject): + def __init__(self, id, name): + self.id = id self.name = name diff --git a/weboob/core/bcall.py b/weboob/core/bcall.py index e74b96e1..504bfad4 100644 --- a/weboob/core/bcall.py +++ b/weboob/core/bcall.py @@ -22,6 +22,8 @@ from copy import copy import logging from logging import debug from threading import Thread, Event, RLock, Timer + +from weboob.capabilities.base import CapBaseObject from weboob.tools.misc import get_backtrace @@ -80,6 +82,8 @@ class BackendsCall(object): def _store_result(self, backend, result): with self.mutex: + if isinstance(result, CapBaseObject): + result.backend = backend.name self.responses.append((backend, result)) self.response_event.set() diff --git a/weboob/tools/application/console.py b/weboob/tools/application/console.py index 70b9ab11..1e59b221 100644 --- a/weboob/tools/application/console.py +++ b/weboob/tools/application/console.py @@ -241,10 +241,9 @@ class ConsoleApplication(BaseApplication): def set_formatter_header(self, string): self.formatter.set_header(string) - def format(self, result, backend_name=None): + def format(self, result): try: - self.formatter.format(obj=result, backend_name=backend_name, - selected_fields=self.selected_fields, condition=self.condition) + self.formatter.format(obj=result, selected_fields=self.selected_fields, condition=self.condition) except FieldNotFound, e: logging.error(e) except ResultsConditionException, e: diff --git a/weboob/tools/application/formatters/iformatter.py b/weboob/tools/application/formatters/iformatter.py index b9a95abc..fda76a82 100644 --- a/weboob/tools/application/formatters/iformatter.py +++ b/weboob/tools/application/formatters/iformatter.py @@ -16,6 +16,7 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +from weboob.capabilities.base import CapBaseObject from weboob.tools.misc import iter_fields from weboob.tools.ordereddict import OrderedDict @@ -43,7 +44,7 @@ class IFormatter(object): def flush(self): raise NotImplementedError() - def format(self, obj, backend_name=None, selected_fields=None, condition=None): + def format(self, obj, selected_fields=None, condition=None): """ Format an object to be human-readable. An object has fields which can be selected, and the objects @@ -52,15 +53,17 @@ class IFormatter(object): call it. It can be used to specify the fields order. @param obj [object] object to format - @param backend_name [str] name of backend, used to create object ID @param selected_fields [tuple] fields to display. If None, all fields are selected @param condition [Condition] condition to objects to display @return a string of the formatted object """ + assert isinstance(obj, (dict, CapBaseObject)) + if isinstance(obj, dict): item = obj else: - item = self.to_dict(obj, backend_name, condition, selected_fields) + item = self.to_dict(obj, condition, selected_fields) + if item is None: return None formatted = self.format_dict(item=item) @@ -82,33 +85,25 @@ class IFormatter(object): def set_header(self, string): raise NotImplementedError() - def to_dict(self, obj, backend_name=None, condition=None, selected_fields=None): + def to_dict(self, obj, condition=None, selected_fields=None): def iter_select_and_decorate(d): - if hasattr(obj, '__id__'): - id_attr = getattr(obj, '__id__') - if not isinstance(id_attr, (set, list, tuple)): - id_attr = (id_attr,) - id_fields = id_attr - else: - id_fields = ('id',) if selected_fields is None or '*' in selected_fields: - for k, v in d: - if k in id_fields and backend_name is not None: - v = self.build_id(v, backend_name) - yield k, v + fields = d.iterkeys() else: - d = dict(d) - for selected_field in selected_fields: - v = d[selected_field] - if selected_field in id_fields and backend_name is not None: - v = self.build_id(v, backend_name) - try: - yield selected_field, v - except KeyError: - raise FieldNotFound(selected_field) + fields = selected_fields - fields_iterator = obj.iter_fields() if hasattr(obj, 'iter_fields') else iter_fields(obj) - d = dict(fields_iterator) + for key in fields: + try: + value = d[key] + except KeyError: + raise FieldNotFound(key) + + if key == 'id' and obj.backend is not None: + value = self.build_id(value, obj.backend) + yield key, value + + fields_iterator = obj.iter_fields() + d = OrderedDict(fields_iterator) if condition is not None and not condition.is_valid(d): return None - return OrderedDict([(k, v) for k, v in iter_select_and_decorate(d.iteritems())]) + return OrderedDict([(k, v) for k, v in iter_select_and_decorate(d)])