change way to describe fields of CapBaseObject, and lot of documentation
This commit is contained in:
parent
99391a95ef
commit
c6a141595c
35 changed files with 1630 additions and 638 deletions
|
|
@ -1,5 +0,0 @@
|
|||
Applications
|
||||
============
|
||||
|
||||
boobank
|
||||
-------
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
Backends
|
||||
========
|
||||
|
||||
cragr
|
||||
-----
|
||||
|
|
@ -10,8 +10,7 @@ def genapi():
|
|||
os.chdir('api')
|
||||
for root, dirs, files in os.walk('../../../weboob/'):
|
||||
root = root.split('/', 4)[-1]
|
||||
if root.startswith('applications') or \
|
||||
root.startswith('backends'):
|
||||
if root.startswith('applications'):
|
||||
continue
|
||||
|
||||
if root.strip():
|
||||
|
|
@ -26,7 +25,7 @@ def genapi():
|
|||
continue
|
||||
|
||||
f, ext = f.rsplit('.', 1)
|
||||
if ext == 'pyc' or f == '__init__':
|
||||
if ext != 'py' or f == '__init__':
|
||||
continue
|
||||
|
||||
subs.add(f)
|
||||
|
|
|
|||
|
|
@ -3,18 +3,12 @@ Weboob
|
|||
|
||||
This is the developer documentation.
|
||||
|
||||
.. warning::
|
||||
This documentation is being written.
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
overview
|
||||
install
|
||||
backends
|
||||
applications
|
||||
guides/index
|
||||
api/index
|
||||
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
Installation
|
||||
============
|
||||
|
||||
If you install Weboob from sources, you'll want to install the Python egg in development mode.
|
||||
You'll be able to update the git repository with remote changes, without re-installing the software.
|
||||
And if you plan to hack on Weboob, you'll see your own changes apply the same way.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
As root:
|
||||
|
||||
``# ./setup.py develop``
|
||||
|
||||
The development mode installation doesn't copies files, but creates an egg-link
|
||||
in the Python system packages directory:
|
||||
|
||||
* ``/usr/lib/python2.5/site-packages`` for Python 2.5
|
||||
* ``/usr/local/lib/python2.6/dist-packages`` for Python 2.6
|
||||
|
||||
Scripts are copied to:
|
||||
|
||||
* ``/usr/bin`` for Python 2.5
|
||||
* ``/usr/local/bin`` for Python 2.6
|
||||
|
||||
Uninstall
|
||||
---------
|
||||
|
||||
* remove the ``/usr/local/lib/python2.6/dist-packages/weboob.egg-link``
|
||||
* remove the weboob line from ``/usr/local/lib/python2.6/dist-packages/easy-install.pth``
|
||||
|
|
@ -3,21 +3,13 @@ Overview
|
|||
|
||||
Weboob (Web Out Of Browsers) provides:
|
||||
|
||||
* :doc:`applications` to interact with websites
|
||||
* :doc:`backends`, each one handles a specific website
|
||||
* :doc:`applications <api/tools/application/index>` to interact with websites
|
||||
* :doc:`backends <api/tools/backend>`, each one handles a specific website
|
||||
* a :doc:`core library <api/core/index>` providing all the features needed by backends
|
||||
* :doc:`tools <api/tools/index>` to help develop backends and applications
|
||||
|
||||
Weboob is written in Python and is distributed under the AGPLv3+ license.
|
||||
|
||||
Why using Weboob?
|
||||
-----------------
|
||||
|
||||
* you get essential information from websites faster
|
||||
* you can write scripts using weboob to automate tasks
|
||||
* you can extend websites features
|
||||
* it helps blind people using crappy websites
|
||||
|
||||
Architecture
|
||||
------------
|
||||
|
||||
|
|
|
|||
|
|
@ -18,23 +18,32 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, Field
|
||||
|
||||
|
||||
__all__ = ['ICapAccount']
|
||||
__all__ = ['AccountRegisterError', 'Account', 'StatusField', 'ICapAccount']
|
||||
|
||||
|
||||
class AccountRegisterError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when there is an error during registration.
|
||||
"""
|
||||
|
||||
class Account(CapBaseObject):
|
||||
"""
|
||||
Describe an account and its properties.
|
||||
"""
|
||||
login = StringField('Login')
|
||||
password = StringField('Password')
|
||||
properties = Field('List of key/value properties', dict)
|
||||
|
||||
def __init__(self, id=None):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('login', basestring)
|
||||
self.add_field('password', basestring)
|
||||
self.add_field('properties', dict)
|
||||
|
||||
class StatusField(object):
|
||||
"""
|
||||
Field of an account status.
|
||||
"""
|
||||
FIELD_TEXT = 0x001 # the value is a long text
|
||||
FIELD_HTML = 0x002 # the value is HTML formated
|
||||
|
||||
|
|
@ -46,8 +55,14 @@ class StatusField(object):
|
|||
|
||||
|
||||
class ICapAccount(IBaseCap):
|
||||
# This class constant may be a list of Value* objects. If the value remains
|
||||
# None, weboob considers that register_account() isn't supported.
|
||||
"""
|
||||
Capability for websites when you can create and manage accounts.
|
||||
|
||||
:var ACCOUNT_REGISTER_PROPERTIES: This class constant may be a list of
|
||||
:class:`weboob.tools.value.Value` objects.
|
||||
If the value remains None, weboob considers
|
||||
that :func:`register_account` isn't supported.
|
||||
"""
|
||||
ACCOUNT_REGISTER_PROPERTIES = None
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -58,7 +73,9 @@ class ICapAccount(IBaseCap):
|
|||
This is a static method, it would be called even if the backend is
|
||||
instancied.
|
||||
|
||||
@param account an Account object which describe the account to create
|
||||
:param account: describe the account to create
|
||||
:type account: :class:`Account`
|
||||
:raises: :class:`AccountRegisterError`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -84,6 +101,6 @@ class ICapAccount(IBaseCap):
|
|||
"""
|
||||
Get status of the current account.
|
||||
|
||||
@return a list of fields
|
||||
:returns: a list of fields
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,30 +18,45 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import datetime, date
|
||||
from datetime import date, datetime
|
||||
|
||||
from .base import CapBaseObject
|
||||
from .base import CapBaseObject, Field, StringField, DateField, FloatField, IntField
|
||||
from .collection import ICapCollection
|
||||
|
||||
|
||||
__all__ = ['Account', 'AccountNotFound', 'TransferError', 'ICapBank', 'Transaction']
|
||||
__all__ = ['AccountNotFound', 'TransferError', 'Recipient', 'Account', 'Transaction', 'Transfer', 'ICapBank']
|
||||
|
||||
|
||||
class AccountNotFound(Exception):
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = 'Account not found'
|
||||
"""
|
||||
Raised when an account is not found.
|
||||
"""
|
||||
|
||||
def __init__(self, msg='Account not found'):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
class TransferError(Exception):
|
||||
pass
|
||||
"""
|
||||
A transfer has failed.
|
||||
"""
|
||||
|
||||
class Recipient(CapBaseObject):
|
||||
"""
|
||||
Recipient of a transfer.
|
||||
"""
|
||||
|
||||
label = StringField('Name')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, 0)
|
||||
self.add_field('label', basestring)
|
||||
|
||||
class Account(Recipient):
|
||||
"""
|
||||
Bank account.
|
||||
|
||||
It is a child class of :class:`Recipient`, because an account can be
|
||||
a recipient of a transfer.
|
||||
"""
|
||||
TYPE_UNKNOWN = 0
|
||||
TYPE_CHECKING = 1 # Transaction, everyday transactions
|
||||
TYPE_SAVINGS = 2 # Savings/Deposit, can be used for everyday banking
|
||||
|
|
@ -50,17 +65,18 @@ class Account(Recipient):
|
|||
TYPE_MARKET = 5 # Stock market or other variable investments
|
||||
TYPE_JOINT = 6 # Joint account
|
||||
|
||||
def __init__(self):
|
||||
Recipient.__init__(self)
|
||||
self.add_field('type', int, self.TYPE_UNKNOWN)
|
||||
self.add_field('balance', float)
|
||||
self.add_field('coming', float)
|
||||
type = IntField('Type of account', default=TYPE_UNKNOWN)
|
||||
balance = FloatField('Balance on this bank account')
|
||||
coming = FloatField('Coming balance')
|
||||
|
||||
def __repr__(self):
|
||||
return u"<Account id=%r label=%r>" % (self.id, self.label)
|
||||
|
||||
|
||||
class Transaction(CapBaseObject):
|
||||
"""
|
||||
Bank transaction.
|
||||
"""
|
||||
TYPE_UNKNOWN = 0
|
||||
TYPE_TRANSFER = 1
|
||||
TYPE_ORDER = 2
|
||||
|
|
@ -72,47 +88,77 @@ class Transaction(CapBaseObject):
|
|||
TYPE_LOAN_PAYMENT = 8
|
||||
TYPE_BANK = 9
|
||||
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('date', (basestring, datetime, date)) # debit date
|
||||
self.add_field('rdate', (datetime, date)) # real date, when the payment has been made
|
||||
self.add_field('type', int, self.TYPE_UNKNOWN)
|
||||
self.add_field('raw', unicode)
|
||||
self.add_field('category', unicode)
|
||||
self.add_field('label', unicode)
|
||||
self.add_field('amount', float)
|
||||
date = DateField('Debit date')
|
||||
rdate = DateField('Real date, when the payment has been made')
|
||||
type = IntField('Type of transaction, use TYPE_* constants', default=TYPE_UNKNOWN)
|
||||
raw = StringField('Raw label of the transaction')
|
||||
category = StringField('Category of transaction')
|
||||
label = StringField('Pretty label')
|
||||
amount = FloatField('Amount of transaction')
|
||||
|
||||
def __repr__(self):
|
||||
return "<Transaction date='%s' label='%s' amount=%s>" % (self.date,
|
||||
self.label, self.amount)
|
||||
|
||||
class Transfer(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('amount', float)
|
||||
self.add_field('date', (basestring, datetime, date))
|
||||
self.add_field('origin', (int, long, basestring))
|
||||
self.add_field('recipient', (int, long, basestring))
|
||||
"""
|
||||
Transfer from an account to a recipient.
|
||||
"""
|
||||
|
||||
amount = FloatField('Amount to transfer')
|
||||
date = Field('Date of transfer', basestring, date, datetime)
|
||||
origin = Field('Origin of transfer', int, long, basestring)
|
||||
recipient = Field('Recipient', int, long, basestring)
|
||||
|
||||
class ICapBank(ICapCollection):
|
||||
"""
|
||||
Capability of bank websites to see accounts and transactions.
|
||||
"""
|
||||
def iter_resources(self, objs, split_path):
|
||||
"""
|
||||
Iter resources.
|
||||
|
||||
Default implementation of this method is to return on top-level
|
||||
all accounts (by calling :func:`iter_accounts`).
|
||||
|
||||
:param objs: type of objects to get
|
||||
:type objs: tuple[:class:`CapBaseObject`]
|
||||
:param split_path: path to discover
|
||||
:type split_path: :class:`list`
|
||||
:rtype: iter[:class:`BaseCapObject`]
|
||||
"""
|
||||
if Account in objs:
|
||||
self._restrict_level(split_path)
|
||||
|
||||
return self.iter_accounts()
|
||||
|
||||
def iter_accounts(self):
|
||||
"""
|
||||
Iter accounts.
|
||||
|
||||
:rtype: iter[:class:`Account`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_account(self, _id):
|
||||
def get_account(self, id):
|
||||
"""
|
||||
Get an account from its ID.
|
||||
|
||||
:param id: ID of the account
|
||||
:type id: :class:`str`
|
||||
:rtype: :class:`Account`
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_history(self, account):
|
||||
"""
|
||||
Iter history of transactions on a specific account.
|
||||
|
||||
@param account [Account]
|
||||
@return [iter(Transaction)]
|
||||
:param account: account to get history
|
||||
:type account: :class:`Account`
|
||||
:rtype: iter[:class:`Transaction`]
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -120,8 +166,10 @@ class ICapBank(ICapCollection):
|
|||
"""
|
||||
Iter coming transactions on a specific account.
|
||||
|
||||
@param account [Account]
|
||||
@return [iter(Transaction)]
|
||||
:param account: account to get coming transactions
|
||||
:type account: :class:`Account`
|
||||
:rtype: iter[:class:`Transaction`]
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -129,8 +177,10 @@ class ICapBank(ICapCollection):
|
|||
"""
|
||||
Iter recipients availables for a transfer from a specific account.
|
||||
|
||||
@param account [Account] account which initiate the transfer
|
||||
@return [iter(Recipient)]
|
||||
:param account: account which initiate the transfer
|
||||
:type account: :class:`Account`
|
||||
:rtype: iter[:class:`Recipient`]
|
||||
:raises: :class:`AccountNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -138,10 +188,15 @@ class ICapBank(ICapCollection):
|
|||
"""
|
||||
Make a transfer from an account to a recipient.
|
||||
|
||||
@param account [Account] account to take money
|
||||
@param recipient [Recipient] account to send money
|
||||
@param amount [float] amount
|
||||
@param reason [str] reason of transfer
|
||||
@return [Transfer] a Transfer object
|
||||
:param account: account to take money
|
||||
:type account: :class:`Account`
|
||||
:param recipient: account to send money
|
||||
:type recipient: :class:`Recipient`
|
||||
:param amount: amount
|
||||
:type amount: :class:`float`
|
||||
:param reason: reason of transfer
|
||||
:type reason: :class:`unicode`
|
||||
:rtype: :class:`Transfer`
|
||||
:raises: :class:`AccountNotFound`, :class:`TransferError`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,28 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from weboob.tools.misc import iter_fields
|
||||
import datetime
|
||||
from dateutil.parser import parse as parse_dt
|
||||
from copy import deepcopy
|
||||
|
||||
from weboob.tools.misc import to_unicode
|
||||
from weboob.tools.ordereddict import OrderedDict
|
||||
|
||||
|
||||
__all__ = ['FieldNotFound', 'IBaseCap', 'NotAvailable', 'NotLoaded',
|
||||
'CapBaseObject']
|
||||
__all__ = ['FieldNotFound', 'NotAvailable', 'NotLoaded', 'IBaseCap',
|
||||
'Field', 'IntField', 'FloatField', 'StringField', 'BytesField',
|
||||
'DateField', 'DeltaField', 'CapBaseObject']
|
||||
|
||||
|
||||
class FieldNotFound(Exception):
|
||||
"""
|
||||
A field isn't found.
|
||||
|
||||
:param obj: object
|
||||
:type obj: :class:`CapBaseObject`
|
||||
:param field: field not found
|
||||
:type field: :class:`Field`
|
||||
"""
|
||||
def __init__(self, obj, field):
|
||||
Exception.__init__(self,
|
||||
u'Field "%s" not found for object %s' % (field, obj))
|
||||
|
|
@ -43,6 +57,9 @@ class NotAvailableMeta(type):
|
|||
|
||||
|
||||
class NotAvailable(object):
|
||||
"""
|
||||
Constant to use on non available fields.
|
||||
"""
|
||||
__metaclass__ = NotAvailableMeta
|
||||
|
||||
|
||||
|
|
@ -58,41 +75,186 @@ class NotLoadedMeta(type):
|
|||
|
||||
|
||||
class NotLoaded(object):
|
||||
"""
|
||||
Constant to use on not loaded fields.
|
||||
|
||||
When you use :func:`weboob.tools.backend.BaseBackend.fillobj` on a object based on :class:`CapBaseObject`,
|
||||
it will request all fields with this value.
|
||||
"""
|
||||
__metaclass__ = NotLoadedMeta
|
||||
|
||||
|
||||
class IBaseCap(object):
|
||||
pass
|
||||
"""
|
||||
This is the base class for all capabilities.
|
||||
|
||||
A capability may define abstract methods (which raise :class:`NotImplementedError`)
|
||||
with an explicit docstring to tell backends how to implement them.
|
||||
|
||||
Also, it may define some *objects*, using :class:`CapBaseObject`.
|
||||
"""
|
||||
|
||||
|
||||
class Field(object):
|
||||
"""
|
||||
Field of a :class:`CapBaseObject` class.
|
||||
|
||||
:param doc: docstring of the field
|
||||
:type doc: :class:`str`
|
||||
:param args: list of types accepted
|
||||
:param default: default value of this field. If not specified, :class:`NotLoaded` is used.
|
||||
"""
|
||||
_creation_counter = 0
|
||||
|
||||
def __init__(self, doc, *args, **kwargs):
|
||||
self.types = ()
|
||||
self.value = kwargs.get('default', NotLoaded)
|
||||
self.doc = doc
|
||||
|
||||
for arg in args:
|
||||
if isinstance(arg, type):
|
||||
self.types += (arg,)
|
||||
else:
|
||||
raise TypeError('Arguments must be types')
|
||||
|
||||
self._creation_counter = Field._creation_counter
|
||||
Field._creation_counter += 1
|
||||
|
||||
def convert(self, value):
|
||||
"""
|
||||
Convert value to the wanted one.
|
||||
"""
|
||||
return value
|
||||
|
||||
class IntField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`int` and :class:`long` types.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, int, long, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
return int(value)
|
||||
|
||||
class FloatField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`float` type.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, float, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
return float(value)
|
||||
|
||||
class StringField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`unicode` strings.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, unicode, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
return to_unicode(value)
|
||||
|
||||
class BytesField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`str` strings.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, str, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode('utf-8')
|
||||
return str(value)
|
||||
|
||||
class DateField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`datetime.date` and :class:`datetime.datetime` types.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, datetime.date, datetime.datetime, **kwargs)
|
||||
|
||||
def convert(self, value):
|
||||
if isinstance(value, basestring):
|
||||
return parse_dt(value)
|
||||
return value
|
||||
|
||||
class TimeField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`datetime.time` and :class:`datetime.time` types.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, datetime.time, datetime.datetime, **kwargs)
|
||||
|
||||
class DeltaField(Field):
|
||||
"""
|
||||
A field which accepts only :class:`datetime.timedelta` type.
|
||||
"""
|
||||
def __init__(self, doc, **kwargs):
|
||||
Field.__init__(self, doc, datetime.timedelta, **kwargs)
|
||||
|
||||
class _CapBaseObjectMeta(type):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
|
||||
fields.sort(key=lambda x: x[1]._creation_counter)
|
||||
|
||||
new_class = super(_CapBaseObjectMeta, cls).__new__(cls, name, bases, attrs)
|
||||
if new_class._fields is None:
|
||||
new_class._fields = OrderedDict()
|
||||
else:
|
||||
new_class._fields = deepcopy(new_class._fields)
|
||||
new_class._fields.update(fields)
|
||||
|
||||
assert new_class.__doc__ is not None
|
||||
if new_class.__doc__ is None:
|
||||
new_class.__doc__ = ''
|
||||
for name, field in fields:
|
||||
doc = '(%s) %s' % (', '.join([':class:`%s`' % v.__name__ for v in field.types]), field.doc)
|
||||
if field.value is not NotLoaded:
|
||||
doc += ' (default: %s)' % field.value
|
||||
new_class.__doc__ += '\n:var %s: %s' % (name, doc)
|
||||
return new_class
|
||||
|
||||
class CapBaseObject(object):
|
||||
FIELDS = None
|
||||
_attribs = None
|
||||
"""
|
||||
This is the base class for a capability object.
|
||||
|
||||
A capability interface may specify to return several kind of objects, to formalise
|
||||
retrieved information from websites.
|
||||
|
||||
As python is a flexible language where variables are not typed, we use a system to
|
||||
force backends to set wanted values on all fields. To do that, we use the :class:`Field`
|
||||
class and all derived ones.
|
||||
|
||||
For example::
|
||||
|
||||
class Transfer(CapBaseObject):
|
||||
" Transfer from an account to a recipient. "
|
||||
|
||||
amount = FloatField('Amount to transfer')
|
||||
date = Field('Date of transfer', basestring, date, datetime)
|
||||
origin = Field('Origin of transfer', int, long, basestring)
|
||||
recipient = Field('Recipient', int, long, basestring)
|
||||
|
||||
The docstring is mandatory.
|
||||
"""
|
||||
|
||||
__metaclass__ = _CapBaseObjectMeta
|
||||
_fields = None
|
||||
|
||||
def __init__(self, id, backend=None):
|
||||
self.id = id
|
||||
self.id = to_unicode(id)
|
||||
self.backend = backend
|
||||
self._fields = deepcopy(self._fields)
|
||||
|
||||
@property
|
||||
def fullid(self):
|
||||
"""
|
||||
Full ID of the object, in form '**ID@backend**'.
|
||||
"""
|
||||
return '%s@%s' % (self.id, self.backend)
|
||||
|
||||
def add_field(self, name, type, value=NotLoaded):
|
||||
"""
|
||||
Add a field in list, which needs to be of type @type.
|
||||
|
||||
@param name [str] name of field
|
||||
@param type [class] type accepted (can be a tuple of types)
|
||||
@param value [object] value set to attribute (default is NotLoaded)
|
||||
"""
|
||||
if not isinstance(self.FIELDS, list):
|
||||
self.FIELDS = []
|
||||
self.FIELDS.append(name)
|
||||
|
||||
if self._attribs is None:
|
||||
self._attribs = {}
|
||||
self._attribs[name] = self._AttribValue(type, value)
|
||||
|
||||
def __iscomplete__(self):
|
||||
"""
|
||||
Return True if the object is completed.
|
||||
|
|
@ -111,6 +273,9 @@ class CapBaseObject(object):
|
|||
def set_empty_fields(self, value, excepts=()):
|
||||
"""
|
||||
Set the same value on all empty fields.
|
||||
|
||||
:param value: value to set on all empty fields
|
||||
:param excepts: if specified, do not change fields listed
|
||||
"""
|
||||
for key, old_value in self.iter_fields():
|
||||
if old_value in (None, NotLoaded, NotAvailable) and \
|
||||
|
|
@ -119,22 +284,16 @@ class CapBaseObject(object):
|
|||
|
||||
def iter_fields(self):
|
||||
"""
|
||||
Iterate on the FIELDS keys and values.
|
||||
Iterate on the fields keys and values.
|
||||
|
||||
Can be overloaded to iterate on other things.
|
||||
|
||||
@return [iter(key,value)] iterator on key, value
|
||||
:rtype: iter[(key, value)]
|
||||
"""
|
||||
|
||||
if self.FIELDS is None:
|
||||
yield 'id', self.id
|
||||
for key, value in iter_fields(self):
|
||||
if key not in ('id', 'backend', 'FIELDS'):
|
||||
yield key, value
|
||||
else:
|
||||
yield 'id', self.id
|
||||
for attrstr in self.FIELDS:
|
||||
yield attrstr, getattr(self, attrstr)
|
||||
for name, field in self._fields.iteritems():
|
||||
yield name, field.value
|
||||
|
||||
def __eq__(self, obj):
|
||||
if isinstance(obj, CapBaseObject):
|
||||
|
|
@ -142,29 +301,32 @@ class CapBaseObject(object):
|
|||
else:
|
||||
return False
|
||||
|
||||
class _AttribValue(object):
|
||||
def __init__(self, type, value):
|
||||
self.type = type
|
||||
self.value = value
|
||||
|
||||
def __getattr__(self, name):
|
||||
if self._attribs is not None and name in self._attribs:
|
||||
return self._attribs[name].value
|
||||
if self._fields is not None and name in self._fields:
|
||||
return self._fields[name].value
|
||||
else:
|
||||
raise AttributeError, "'%s' object has no attribute '%s'" % (
|
||||
self.__class__.__name__, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
try:
|
||||
attr = (self._attribs or {})[name]
|
||||
attr = (self._fields or {})[name]
|
||||
except KeyError:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
if not isinstance(value, attr.type) and \
|
||||
try:
|
||||
# Try to convert value to the wanted one.
|
||||
value = attr.convert(value)
|
||||
except Exception:
|
||||
# error during conversion, it will probably not
|
||||
# match the wanted following types, so we'll
|
||||
# raise ValueError.
|
||||
pass
|
||||
if not isinstance(value, attr.types) and \
|
||||
value is not NotLoaded and \
|
||||
value is not NotAvailable and \
|
||||
value is not None:
|
||||
raise ValueError(
|
||||
'Value for "%s" needs to be of type %r, not %r' % (
|
||||
name, attr.type, type(value)))
|
||||
name, attr.types, type(value)))
|
||||
attr.value = value
|
||||
|
|
|
|||
|
|
@ -17,74 +17,133 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, date
|
||||
from .base import CapBaseObject
|
||||
from .base import CapBaseObject, StringField, DateField, FloatField
|
||||
from .collection import ICapCollection
|
||||
|
||||
|
||||
__all__ = ['Subscription', 'SubscriptionNotFound', 'ICapBill', 'Detail']
|
||||
__all__ = ['SubscriptionNotFound', 'BillNotFound', 'Detail', 'Bill', 'Subscription', 'ICapBill']
|
||||
|
||||
|
||||
class SubscriptionNotFound(Exception):
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = 'Subscription not found'
|
||||
"""
|
||||
Raised when a subscription is not found.
|
||||
"""
|
||||
def __init__(self, msg='Subscription not found'):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
|
||||
class BillNotFound(Exception):
|
||||
def __init__(self, msg=None):
|
||||
if msg is None:
|
||||
msg = 'Bill not found'
|
||||
"""
|
||||
Raised when a bill is not found.
|
||||
"""
|
||||
def __init__(self, msg='Bill not found'):
|
||||
Exception.__init__(self, msg)
|
||||
|
||||
|
||||
class Detail(CapBaseObject):
|
||||
"""
|
||||
Detail of a subscription
|
||||
"""
|
||||
label = StringField('label of the detail line')
|
||||
infos = StringField('information')
|
||||
datetime = DateField('date information')
|
||||
price = FloatField('price')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, 0)
|
||||
self.add_field('label', basestring)
|
||||
self.add_field('infos', basestring)
|
||||
self.add_field('datetime', datetime)
|
||||
self.add_field('price', float)
|
||||
|
||||
class Bill(CapBaseObject):
|
||||
"""
|
||||
Bill.
|
||||
"""
|
||||
date = DateField('date of the bill')
|
||||
format = StringField('format of the bill')
|
||||
label = StringField('label of bill')
|
||||
idparent = StringField('id of the parent subscription')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, 0)
|
||||
self.add_field('date', date)
|
||||
self.add_field('format', basestring)
|
||||
self.add_field('label', basestring)
|
||||
self.add_field('idparent', basestring)
|
||||
|
||||
class Subscription(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('label', basestring)
|
||||
self.add_field('subscriber', basestring)
|
||||
"""
|
||||
Subscription to a service.
|
||||
"""
|
||||
label = StringField('label of subscription')
|
||||
subscriber = StringField('whe has subscribed')
|
||||
|
||||
class ICapBill(ICapCollection):
|
||||
def iter_resources(self, objs, split_path):
|
||||
"""
|
||||
Iter resources. Will return :func:`iter_subscriptions`.
|
||||
"""
|
||||
if Subscription in objs:
|
||||
self._restrict_level(split_path)
|
||||
|
||||
return self.iter_subscription()
|
||||
|
||||
def iter_subscription(self):
|
||||
"""
|
||||
Iter subscriptions.
|
||||
|
||||
:rtype: iter[:class:`Subscription`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_subscription(self, _id):
|
||||
"""
|
||||
Get a subscription.
|
||||
|
||||
:param _id: ID of subscription
|
||||
:rtype: :class:`Subscription`
|
||||
:raises: :class:`SubscriptionNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_history(self, subscription):
|
||||
"""
|
||||
Iter history of a subscription.
|
||||
|
||||
:param subscription: subscription to get history
|
||||
:type subscription: :class:`Subscription`
|
||||
:rtype: iter[:class:`Detail`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_bill(self, id):
|
||||
"""
|
||||
Get a bill.
|
||||
|
||||
:param id: ID of bill
|
||||
:rtype: :class:`Bill`
|
||||
:raises: :class:`BillNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def download_bill(self, id):
|
||||
"""
|
||||
Download a bill.
|
||||
|
||||
:param id: ID of bill
|
||||
:rtype: str
|
||||
:raises: :class:`BillNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_bills(self, subscription):
|
||||
"""
|
||||
Iter bills.
|
||||
|
||||
:param subscription: subscription to get bills
|
||||
:type subscription: :class:`Subscription`
|
||||
:rtype: iter[:class:`Bill`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_details(self, subscription):
|
||||
"""
|
||||
Get details of a subscription.
|
||||
|
||||
:param subscription: subscription to get bills
|
||||
:type subscription: :class:`Subscription`
|
||||
:rtype: iter[:class:`Detail`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,30 +17,49 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, DateField, \
|
||||
IntField, DeltaField
|
||||
|
||||
|
||||
__all__ = ['ICapBugTracker']
|
||||
__all__ = ['IssueError', 'Project', 'User', 'Version', 'Status', 'Attachment',
|
||||
'Change', 'Update', 'Issue', 'Query', 'ICapBugTracker']
|
||||
|
||||
|
||||
class IssueError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when there is an error with an issue.
|
||||
"""
|
||||
|
||||
class Project(CapBaseObject):
|
||||
"""
|
||||
Represents a project.
|
||||
"""
|
||||
name = StringField('Name of the project')
|
||||
members = Field('Members of projects', list)
|
||||
versions = Field('List of versions available for this project', list)
|
||||
categories = Field('All categories', list)
|
||||
statuses = Field('Available statuses for issues', list)
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.add_field('members', list)
|
||||
self.add_field('versions', list)
|
||||
self.add_field('categories', list)
|
||||
self.add_field('statuses', list)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<Project %r>' % self.name
|
||||
|
||||
def find_user(self, id, name):
|
||||
"""
|
||||
Find a user from its ID.
|
||||
|
||||
If not found, create a :class:`User` with the specified name.
|
||||
|
||||
:param id: ID of user
|
||||
:type id: str
|
||||
:param name: Name of user
|
||||
:type name: str
|
||||
:rtype: :class:`User`
|
||||
"""
|
||||
for user in self.members:
|
||||
if user.id == id:
|
||||
return user
|
||||
|
|
@ -49,6 +68,17 @@ class Project(CapBaseObject):
|
|||
return User(id, name)
|
||||
|
||||
def find_version(self, id, name):
|
||||
"""
|
||||
Find a version from an ID.
|
||||
|
||||
If not found, create a :class:`Version` with the specified name.
|
||||
|
||||
:param id: ID of version
|
||||
:type id: str
|
||||
:param name: Name of version
|
||||
:type name: str
|
||||
:rtype: :class:`Version`
|
||||
"""
|
||||
for version in self.versions:
|
||||
if version.id == id:
|
||||
return version
|
||||
|
|
@ -57,6 +87,13 @@ class Project(CapBaseObject):
|
|||
return Version(id, name)
|
||||
|
||||
def find_status(self, name):
|
||||
"""
|
||||
Find a status from a name.
|
||||
|
||||
:param name: Name of status
|
||||
:type name: str
|
||||
:rtype: :class:`Status`
|
||||
"""
|
||||
for status in self.statuses:
|
||||
if status.name == name:
|
||||
return status
|
||||
|
|
@ -65,98 +102,129 @@ class Project(CapBaseObject):
|
|||
return None
|
||||
|
||||
class User(CapBaseObject):
|
||||
"""
|
||||
User.
|
||||
"""
|
||||
name = StringField('Name of user')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % self.name
|
||||
|
||||
class Version(CapBaseObject):
|
||||
"""
|
||||
Version of a project.
|
||||
"""
|
||||
name = StringField('Name of version')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return '<Version %r>' % self.name
|
||||
|
||||
class Status(CapBaseObject):
|
||||
"""
|
||||
Status of an issue.
|
||||
|
||||
**VALUE_** constants are the primary status
|
||||
types.
|
||||
"""
|
||||
(VALUE_NEW,
|
||||
VALUE_PROGRESS,
|
||||
VALUE_RESOLVED,
|
||||
VALUE_REJECTED) = range(4)
|
||||
|
||||
name = StringField('Name of status')
|
||||
value = IntField('Value of status (constants VALUE_*)')
|
||||
|
||||
def __init__(self, id, name, value):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', unicode, name)
|
||||
self.add_field('value', int, value)
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def __repr__(self):
|
||||
return '<Status %r>' % self.name
|
||||
|
||||
class Attachment(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('filename', basestring)
|
||||
self.add_field('url', basestring)
|
||||
"""
|
||||
Attachment of an issue.
|
||||
"""
|
||||
filename = StringField('Filename')
|
||||
url = StringField('Direct URL to attachment')
|
||||
|
||||
def __repr__(self):
|
||||
return '<Attachment %r>' % self.filename
|
||||
|
||||
class Change(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('field', unicode)
|
||||
self.add_field('last', unicode)
|
||||
self.add_field('new', unicode)
|
||||
"""
|
||||
A change of an update.
|
||||
"""
|
||||
field = StringField('What field has been changed')
|
||||
last = StringField('Last value of field')
|
||||
new = StringField('New value of field')
|
||||
|
||||
class Update(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('author', User)
|
||||
self.add_field('date', datetime)
|
||||
self.add_field('hours', timedelta)
|
||||
self.add_field('message', unicode)
|
||||
self.add_field('attachments', (list,tuple)) # Attachment
|
||||
self.add_field('changes', (list,tuple)) # Change
|
||||
"""
|
||||
Represents an update of an issue.
|
||||
"""
|
||||
author = Field('Author of update', User)
|
||||
date = DateField('Date of update')
|
||||
hours = DeltaField('Time activity')
|
||||
message = StringField('Log message')
|
||||
attachments = Field('Files attached to update', list, tuple)
|
||||
changes = Field('List of changes', list, tuple)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Update %r>' % self.id
|
||||
|
||||
class Issue(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('project', Project)
|
||||
self.add_field('title', unicode)
|
||||
self.add_field('body', unicode)
|
||||
self.add_field('creation', datetime)
|
||||
self.add_field('updated', datetime)
|
||||
self.add_field('attachments', (list,tuple))
|
||||
self.add_field('history', (list,tuple))
|
||||
self.add_field('author', User)
|
||||
self.add_field('assignee', User)
|
||||
self.add_field('category', unicode)
|
||||
self.add_field('version', Version)
|
||||
self.add_field('status', Status)
|
||||
"""
|
||||
Represents an issue.
|
||||
"""
|
||||
project = Field('Project of this issue', Project)
|
||||
title = StringField('Title of issue')
|
||||
body = StringField('Text of issue')
|
||||
creation = DateField('Date when this issue has been created')
|
||||
updated = DateField('Date when this issue has been updated for the last time')
|
||||
attachments = Field('List of attached files', list, tuple)
|
||||
history = Field('History of updates', list, tuple)
|
||||
author = Field('Author of this issue', User)
|
||||
assignee = Field('User assigned to this issue', User)
|
||||
category = StringField('Name of the category')
|
||||
version = Field('Target version of this issue', Version)
|
||||
status = Field('Status of this issue', Status)
|
||||
|
||||
class Query(CapBaseObject):
|
||||
"""
|
||||
Query to find an issue.
|
||||
"""
|
||||
project = StringField('Filter on projects')
|
||||
title = StringField('Filter on titles')
|
||||
author = StringField('Filter on authors')
|
||||
assignee = StringField('Filter on assignees')
|
||||
version = StringField('Filter on versions')
|
||||
category = StringField('Filter on categories')
|
||||
status = StringField('Filter on statuses')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, '')
|
||||
self.add_field('project', unicode)
|
||||
self.add_field('title', unicode)
|
||||
self.add_field('author', unicode)
|
||||
self.add_field('assignee', unicode)
|
||||
self.add_field('version', unicode)
|
||||
self.add_field('category', unicode)
|
||||
self.add_field('status', unicode)
|
||||
|
||||
class ICapBugTracker(IBaseCap):
|
||||
"""
|
||||
Bug trackers websites.
|
||||
"""
|
||||
def iter_issues(self, query):
|
||||
"""
|
||||
Iter issues with optionnal patterns.
|
||||
|
||||
@param query [Query]
|
||||
@return [iter(Issue)] issues
|
||||
:param query: query
|
||||
:type query: :class:`Query`
|
||||
:rtype: iter[:class:`Issue`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -164,7 +232,8 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Get an issue from its ID.
|
||||
|
||||
@return Issue
|
||||
:param id: ID of issue
|
||||
:rtype: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -172,13 +241,19 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Create an empty issue on the given project.
|
||||
|
||||
@return [Issue] the created issue.
|
||||
:param project: project
|
||||
:type project: :class:`Project`
|
||||
:returns: the created issue
|
||||
:rtype: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def post_issue(self, issue):
|
||||
"""
|
||||
Post an issue to create or update it.
|
||||
|
||||
:param issue: issue to create or update
|
||||
:type issue: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -186,14 +261,19 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Add an update to an issue.
|
||||
|
||||
@param issue [id,Issue] issue or id of issue
|
||||
@param update [Update] an Update object
|
||||
:param issue: issue or id of issue
|
||||
:type issue: :class:`Issue`
|
||||
:param update: an Update object
|
||||
:type update: :class:`Update`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def remove_issue(self, issue):
|
||||
"""
|
||||
Remove an issue.
|
||||
|
||||
:param issue: issue
|
||||
:type issue: :class:`Issue`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -201,7 +281,7 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Iter projects.
|
||||
|
||||
@return [iter(Project)] projects
|
||||
:rtype: iter[:class:`Project`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -209,6 +289,6 @@ class ICapBugTracker(IBaseCap):
|
|||
"""
|
||||
Get a project from its ID.
|
||||
|
||||
@return [Project]
|
||||
:rtype: :class:`Project`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -20,31 +20,60 @@
|
|||
|
||||
import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, DateField
|
||||
|
||||
|
||||
__all__ = ['ChatException', 'ICapChat']
|
||||
__all__ = ['ChatException', 'ChatMessage', 'ICapChat']
|
||||
|
||||
|
||||
class ChatException(Exception):
|
||||
pass
|
||||
"""
|
||||
Exception raised when there is a problem with the chat.
|
||||
"""
|
||||
|
||||
|
||||
class ChatMessage(CapBaseObject):
|
||||
"""
|
||||
Message on the chat.
|
||||
"""
|
||||
id_from = StringField('ID of sender')
|
||||
id_to = StringField('ID of recipient')
|
||||
message = StringField('Content of message')
|
||||
date = DateField('Date when the message has been sent')
|
||||
|
||||
def __init__(self, id_from, id_to, message, date=None):
|
||||
CapBaseObject.__init__(self, '%s.%s' % (id_from, id_to))
|
||||
self.add_field('id_from', basestring, id_from)
|
||||
self.add_field('id_to', basestring, id_to)
|
||||
self.add_field('message', basestring, message)
|
||||
self.add_field('date', datetime.datetime, date)
|
||||
self.id_from = id_from
|
||||
self.id_to = id_to
|
||||
self.message = message
|
||||
self.date = date
|
||||
|
||||
if self.date is None:
|
||||
self.date = datetime.datetime.utcnow()
|
||||
|
||||
|
||||
class ICapChat(IBaseCap):
|
||||
"""
|
||||
Websites with a chat system.
|
||||
"""
|
||||
def iter_chat_messages(self, _id=None):
|
||||
"""
|
||||
Iter messages.
|
||||
|
||||
:param _id: optional parameter to only get messages
|
||||
from a given contact.
|
||||
:type _id: str
|
||||
:rtype: iter[:class:`ChatMessage`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def send_chat_message(self, _id, message):
|
||||
"""
|
||||
Send a message to a contact.
|
||||
|
||||
:param _id: ID of recipient
|
||||
:type _id: str
|
||||
:param message: message to send
|
||||
:type message: str
|
||||
:raises: :class:`ChatException`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,17 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, BytesField, IntField
|
||||
from weboob.tools.ordereddict import OrderedDict
|
||||
|
||||
|
||||
__all__ = ['ICapContact', 'Contact']
|
||||
__all__ = ['ProfileNode', 'ContactPhoto', 'Contact', 'QueryError', 'Query', 'ICapContact']
|
||||
|
||||
|
||||
class ProfileNode(object):
|
||||
"""
|
||||
Node of a :class:`Contact` profile.
|
||||
"""
|
||||
HEAD = 0x01
|
||||
SECTION = 0x02
|
||||
|
||||
|
|
@ -41,14 +44,19 @@ class ProfileNode(object):
|
|||
|
||||
|
||||
class ContactPhoto(CapBaseObject):
|
||||
"""
|
||||
Photo of a contact.
|
||||
"""
|
||||
name = StringField('Name of the photo')
|
||||
url = StringField('Direct URL to photo')
|
||||
data = BytesField('Data of photo')
|
||||
thumbnail_url = StringField('Direct URL to thumbnail')
|
||||
thumbnail_data = BytesField('Data of thumbnail')
|
||||
hidden = Field('True if the photo is hidden on website', bool)
|
||||
|
||||
def __init__(self, name):
|
||||
CapBaseObject.__init__(self, name)
|
||||
self.add_field('name', basestring, name)
|
||||
self.add_field('url', basestring)
|
||||
self.add_field('data', str)
|
||||
self.add_field('thumbnail_url', basestring)
|
||||
self.add_field('thumbnail_data', basestring)
|
||||
self.add_field('hidden', bool, False)
|
||||
self.name = name
|
||||
|
||||
def __iscomplete__(self):
|
||||
return (self.data and (not self.thumbnail_url or self.thumbnail_data))
|
||||
|
|
@ -63,22 +71,35 @@ class ContactPhoto(CapBaseObject):
|
|||
|
||||
|
||||
class Contact(CapBaseObject):
|
||||
"""
|
||||
A contact.
|
||||
"""
|
||||
STATUS_ONLINE = 0x001
|
||||
STATUS_AWAY = 0x002
|
||||
STATUS_OFFLINE = 0x004
|
||||
STATUS_ALL = 0xfff
|
||||
|
||||
name = StringField('Name of contact')
|
||||
status = IntField('Status of contact (STATUS_* constants)')
|
||||
url = StringField('URL to the profile of contact')
|
||||
status_msg = StringField('Message of status')
|
||||
summary = StringField('Description of contact')
|
||||
photos = Field('List of photos', dict, default=OrderedDict())
|
||||
profile = Field('Contact profile', dict)
|
||||
|
||||
def __init__(self, id, name, status):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring, name)
|
||||
self.add_field('status', int, status)
|
||||
self.add_field('url', basestring)
|
||||
self.add_field('status_msg', basestring)
|
||||
self.add_field('summary', basestring)
|
||||
self.add_field('photos', dict, OrderedDict())
|
||||
self.add_field('profile', dict)
|
||||
self.name = name
|
||||
self.status = status
|
||||
|
||||
def set_photo(self, name, **kwargs):
|
||||
"""
|
||||
Set photo of contact.
|
||||
|
||||
:param name: name of photo
|
||||
:type name: str
|
||||
:param kwargs: See :class:`ContactPhoto` to know what other parameters you can use
|
||||
"""
|
||||
if not name in self.photos:
|
||||
self.photos[name] = ContactPhoto(name)
|
||||
|
||||
|
|
@ -88,13 +109,20 @@ class Contact(CapBaseObject):
|
|||
|
||||
|
||||
class QueryError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when unable to send a query to a contact.
|
||||
"""
|
||||
|
||||
|
||||
class Query(CapBaseObject):
|
||||
"""
|
||||
Query to send to a contact.
|
||||
"""
|
||||
message = StringField('Message received')
|
||||
|
||||
def __init__(self, id, message):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('message', basestring, message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class ICapContact(IBaseCap):
|
||||
|
|
@ -102,9 +130,11 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Iter contacts
|
||||
|
||||
@param status get only contacts with the specified status
|
||||
@param ids if set, get the specified contacts
|
||||
@return iterator over the contacts found
|
||||
:param status: get only contacts with the specified status
|
||||
:type status: Contact.STATUS_*
|
||||
:param ids: if set, get the specified contacts
|
||||
:type ids: list[str]
|
||||
:rtype: iter[:class:`Contact`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -116,8 +146,9 @@ class ICapContact(IBaseCap):
|
|||
with the proper values, but it might be overloaded
|
||||
by backends.
|
||||
|
||||
@param id the ID requested
|
||||
@return the Contact object, or None if not found
|
||||
:param id: the ID requested
|
||||
:type id: str
|
||||
:rtype: :class:`Contact` or None if not found
|
||||
"""
|
||||
|
||||
l = self.iter_contacts(ids=[id])
|
||||
|
|
@ -130,9 +161,10 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Send a query to a contact
|
||||
|
||||
@param id the ID of contact
|
||||
@return a Query object
|
||||
@except QueryError
|
||||
:param id: the ID of contact
|
||||
:type id: str
|
||||
:rtype: :class:`Query`
|
||||
:raises: :class:`QueryError`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -140,8 +172,9 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Get personal notes about a contact
|
||||
|
||||
@param id the ID of the contact
|
||||
@return a unicode object
|
||||
:param id: the ID of the contact
|
||||
:type id: str
|
||||
:rtype: unicode
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
|
@ -149,7 +182,8 @@ class ICapContact(IBaseCap):
|
|||
"""
|
||||
Set personal notes about a contact
|
||||
|
||||
@param id the ID of the contact
|
||||
@param notes the unicode object to save as notes
|
||||
:param id: the ID of the contact
|
||||
:type id: str
|
||||
:returns: the unicode object to save as notes
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
|
|
|||
|
|
@ -18,37 +18,74 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from datetime import datetime
|
||||
from .base import IBaseCap, CapBaseObject, StringField, DateField, Field
|
||||
|
||||
|
||||
__all__ = ['Content', 'Revision', 'ICapContent']
|
||||
|
||||
|
||||
class Content(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('title', basestring)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('content', basestring)
|
||||
self.add_field('revision', basestring)
|
||||
"""
|
||||
Content object.
|
||||
"""
|
||||
title = StringField('Title of content')
|
||||
author = StringField('Original author of content')
|
||||
content = StringField('Body')
|
||||
revision = StringField('ID of revision')
|
||||
|
||||
class Revision(CapBaseObject):
|
||||
def __init__(self, _id):
|
||||
CapBaseObject.__init__(self, _id)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('comment', basestring)
|
||||
self.add_field('revision', basestring)
|
||||
self.add_field('timestamp', datetime)
|
||||
self.add_field('minor', bool)
|
||||
|
||||
|
||||
"""
|
||||
Revision of a change on a content.
|
||||
"""
|
||||
author = StringField('Author of revision')
|
||||
comment = StringField('Comment log about revision')
|
||||
timestamp = DateField('Date of revision')
|
||||
minor = Field('Is this change minor?', bool)
|
||||
|
||||
class ICapContent(IBaseCap):
|
||||
def get_content(self, id, revision=None):
|
||||
"""
|
||||
Get a content from an ID.
|
||||
|
||||
:param id: ID of content
|
||||
:type id: str
|
||||
:param revision: if given, get the content at this revision
|
||||
:type revision: :class:`Revision`
|
||||
:rtype: :class:`Content`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_revisions(self, id, max_results=10):
|
||||
"""
|
||||
Iter revisions of a content.
|
||||
|
||||
:param id: id of content
|
||||
:type id: str
|
||||
:param max_results: maximum results
|
||||
:type max_results: int
|
||||
:rtype: iter[:class:`Revision`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def push_content(self, content, message=None, minor=False):
|
||||
"""
|
||||
Push a new revision of a content.
|
||||
|
||||
:param content: object to push
|
||||
:type content: :class:`Content`
|
||||
:param message: log message to associate to new revision
|
||||
:type message: str
|
||||
:param minor: this is a minor revision
|
||||
:type minor: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_content_preview(self, content):
|
||||
"""
|
||||
Get a HTML preview of a content.
|
||||
|
||||
:param content: content object
|
||||
:type content: :class:`Content`
|
||||
:rtype: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,55 +18,103 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, DateField
|
||||
from .contact import Contact
|
||||
|
||||
|
||||
__all__ = ['ICapDating']
|
||||
__all__ = ['OptimizationNotFound', 'Optimization', 'Event', 'ICapDating']
|
||||
|
||||
|
||||
class OptimizationNotFound(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when an optimization is not found.
|
||||
"""
|
||||
|
||||
|
||||
class Optimization(object):
|
||||
# Configuration of optim can be made by Value*s in this dict.
|
||||
"""
|
||||
Optimization.
|
||||
|
||||
:var CONFIG: Configuration of optim can be made by
|
||||
:class:`weboob.tools.value.Value` objects
|
||||
in this dict.
|
||||
"""
|
||||
CONFIG = {}
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Start optimization.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def stop(self):
|
||||
"""
|
||||
Stop optimization.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def is_running(self):
|
||||
"""
|
||||
Know if the optimization is currently running.
|
||||
|
||||
:rtype: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_config(self):
|
||||
"""
|
||||
Get config of this optimization.
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
return None
|
||||
|
||||
def set_config(self, params):
|
||||
"""
|
||||
Set config of this optimization.
|
||||
|
||||
:param params: parameters
|
||||
:type params: dict
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class Event(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('date', (datetime.datetime))
|
||||
self.add_field('contact', Contact)
|
||||
self.add_field('type', basestring)
|
||||
self.add_field('message', basestring)
|
||||
"""
|
||||
A dating event (for example a visite, a query received, etc.)
|
||||
"""
|
||||
date = DateField('Date of event')
|
||||
contact = Field('Contact related to this event', Contact)
|
||||
type = StringField('Type of event')
|
||||
message = StringField('Message of the event')
|
||||
|
||||
class ICapDating(IBaseCap):
|
||||
"""
|
||||
Capability for dating websites.
|
||||
"""
|
||||
def init_optimizations(self):
|
||||
"""
|
||||
Initialization of optimizations.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def add_optimization(self, name, optim):
|
||||
"""
|
||||
Add an optimization.
|
||||
|
||||
:param name: name of optimization
|
||||
:type name: str
|
||||
:param optim: optimization
|
||||
:type optim: :class:`Optimization`
|
||||
"""
|
||||
setattr(self, 'OPTIM_%s' % name, optim)
|
||||
|
||||
def iter_optimizations(self, *optims):
|
||||
def iter_optimizations(self):
|
||||
"""
|
||||
Iter optimizations.
|
||||
|
||||
:rtype: iter[:class:`Optimization`]
|
||||
"""
|
||||
for attr_name in dir(self):
|
||||
if not attr_name.startswith('OPTIM_'):
|
||||
continue
|
||||
|
|
@ -77,6 +125,13 @@ class ICapDating(IBaseCap):
|
|||
yield attr_name[6:], attr
|
||||
|
||||
def get_optimization(self, optim):
|
||||
"""
|
||||
Get an optimization from a name.
|
||||
|
||||
:param optim: name of optimization
|
||||
:type optim: str
|
||||
:rtype: :class:`Optimization`
|
||||
"""
|
||||
optim = optim.upper()
|
||||
if not hasattr(self, 'OPTIM_%s' % optim):
|
||||
raise OptimizationNotFound()
|
||||
|
|
@ -84,4 +139,9 @@ class ICapDating(IBaseCap):
|
|||
return getattr(self, 'OPTIM_%s' % optim)
|
||||
|
||||
def iter_events(self):
|
||||
"""
|
||||
Iter events.
|
||||
|
||||
:rtype: iter[:class:`Event`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,31 +17,39 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from weboob.tools.capabilities.thumbnail import Thumbnail
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded, Field, StringField, \
|
||||
BytesField, IntField, FloatField, DateField
|
||||
|
||||
__all__ = ['Thumbnail', 'ICapGallery', 'BaseGallery', 'BaseImage']
|
||||
__all__ = ['BaseGallery', 'BaseImage', 'ICapGallery']
|
||||
|
||||
|
||||
class BaseGallery(CapBaseObject):
|
||||
"""
|
||||
Represents a gallery.
|
||||
|
||||
This object has to be inherited to specify how to calculate the URL of the gallery from its ID.
|
||||
"""
|
||||
title = StringField('Title of gallery')
|
||||
url = StringField('Direct URL to gallery')
|
||||
description = StringField('Description of gallery')
|
||||
cardinality = IntField('Cardinality of gallery')
|
||||
date = DateField('Date of gallery')
|
||||
rating = FloatField('Rating of this gallery')
|
||||
rating_max = FloatField('Max rating available')
|
||||
thumbnail = Field('Thumbnail', Thumbnail)
|
||||
|
||||
def __init__(self, _id, title=NotLoaded, url=NotLoaded, cardinality=NotLoaded, date=NotLoaded,
|
||||
rating=NotLoaded, rating_max=NotLoaded, thumbnail=NotLoaded, thumbnail_url=None, nsfw=False):
|
||||
CapBaseObject.__init__(self, unicode(_id))
|
||||
|
||||
self.add_field('title', basestring, title)
|
||||
self.add_field('url', basestring, url)
|
||||
self.add_field('description', basestring)
|
||||
self.add_field('cardinality', int)
|
||||
self.add_field('date', datetime, date)
|
||||
self.add_field('rating', (int, long, float), rating)
|
||||
self.add_field('rating_max', (int, long, float), rating_max)
|
||||
self.add_field('thumbnail', Thumbnail, thumbnail)
|
||||
self.title = title
|
||||
self.url = url
|
||||
self.date = date
|
||||
self.rating = rating
|
||||
self.rating_max = rating_max
|
||||
self.thumbnail = thumbnail
|
||||
|
||||
@classmethod
|
||||
def id2url(cls, _id):
|
||||
|
|
@ -50,24 +58,39 @@ class BaseGallery(CapBaseObject):
|
|||
|
||||
@property
|
||||
def page_url(self):
|
||||
"""
|
||||
Get URL to page of this gallery.
|
||||
"""
|
||||
return self.id2url(self.id)
|
||||
|
||||
def iter_image(self):
|
||||
"""
|
||||
Iter images.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BaseImage(CapBaseObject):
|
||||
"""
|
||||
Base class for images.
|
||||
"""
|
||||
index = IntField('Usually page number')
|
||||
thumbnail = Field('Thumbnail of the image', Thumbnail)
|
||||
url = StringField('Direct URL to image')
|
||||
ext = StringField('Extension of image')
|
||||
data = BytesField('Data of image')
|
||||
gallery = Field('Reference to the Gallery object', BaseGallery)
|
||||
|
||||
def __init__(self, _id, index=None, thumbnail=NotLoaded, url=NotLoaded,
|
||||
ext=NotLoaded, gallery=None):
|
||||
|
||||
CapBaseObject.__init__(self, unicode(_id))
|
||||
|
||||
self.add_field('index', int, index) # usually page number
|
||||
self.add_field('thumbnail', Thumbnail, thumbnail)
|
||||
self.add_field('url', basestring, url)
|
||||
self.add_field('ext', basestring, ext)
|
||||
self.add_field('data', str)
|
||||
self.add_field('gallery', BaseGallery, gallery)
|
||||
self.index = index
|
||||
self.thumbnail = thumbnail
|
||||
self.url = url
|
||||
self.ext = ext
|
||||
self.gallery = gallery
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
|
@ -92,10 +115,15 @@ class ICapGallery(IBaseCap):
|
|||
"""
|
||||
Iter results of a search on a pattern.
|
||||
|
||||
@param pattern [str] pattern to search on
|
||||
@param sortby [enum] sort by...
|
||||
@param nsfw [bool] include non-suitable for work videos if True
|
||||
@param max_results [int] maximum number of results to return
|
||||
:param pattern: pattern to search on
|
||||
:type pattern: str
|
||||
:param sortby: sort by...
|
||||
:type sortby: SEARCH_*
|
||||
:param nsfw: include non-suitable for work videos if True
|
||||
:type nsfw: bool
|
||||
:param max_results: maximum number of results to return
|
||||
:type max_results: int
|
||||
:rtype: :class:`BaseGallery`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -103,7 +131,8 @@ class ICapGallery(IBaseCap):
|
|||
"""
|
||||
Get gallery from an ID.
|
||||
|
||||
@param _id the gallery id. It can be a numeric ID, or a page url, or so.
|
||||
@return a Gallery object
|
||||
:param _id: the gallery id. It can be a numeric ID, or a page url, or so.
|
||||
:type _id: str
|
||||
:rtype: :class:`Gallery`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -16,39 +16,42 @@
|
|||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, FloatField, DateField
|
||||
|
||||
|
||||
__all__ = ['ICapWaterLevel']
|
||||
__all__ = ['Gauge', 'GaugeMeasure', 'ICapWaterLevel']
|
||||
|
||||
|
||||
class Gauge(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
|
||||
self.add_field('name', basestring)
|
||||
self.add_field('river', basestring)
|
||||
self.add_field('level', float)
|
||||
self.add_field('flow', float)
|
||||
self.add_field('lastdate', datetime)
|
||||
self.add_field('forecast', basestring)
|
||||
"""
|
||||
Gauge class.
|
||||
"""
|
||||
name = StringField('Name of gauge')
|
||||
river = StringField('What river')
|
||||
level = FloatField('Level of gauge')
|
||||
flow = FloatField('Flow of gauge')
|
||||
lastdate = DateField('Last measure')
|
||||
forecast = StringField('Forecast')
|
||||
|
||||
class GaugeMeasure(CapBaseObject):
|
||||
"""
|
||||
Measure of a gauge.
|
||||
"""
|
||||
level = FloatField('Level of measure')
|
||||
flow = FloatField('Flow of measure')
|
||||
date = DateField('Date of measure')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, None)
|
||||
|
||||
self.add_field('level', float)
|
||||
self.add_field('flow', float)
|
||||
self.add_field('date', datetime)
|
||||
|
||||
class ICapWaterLevel(IBaseCap):
|
||||
def iter_gauge_history(self, id):
|
||||
"""
|
||||
Get history of a gauge.
|
||||
|
||||
@param id [str] ID of the river
|
||||
@return [iter(GaugeMeasure)]
|
||||
:param id: ID of the river
|
||||
:type id: str
|
||||
:rtype: iter[:class:`GaugeMeasure`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -56,8 +59,9 @@ class ICapWaterLevel(IBaseCap):
|
|||
"""
|
||||
Get last measure of the gauge.
|
||||
|
||||
@param id [str] ID of the gauge.
|
||||
@return [GaugeMeasure]
|
||||
:param id: ID of the gauge
|
||||
:type id: str
|
||||
:rtype: :class:`GaugeMeasure`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -65,7 +69,8 @@ class ICapWaterLevel(IBaseCap):
|
|||
"""
|
||||
Iter gauges.
|
||||
|
||||
@param pattern [str] if specified, used to search gauges
|
||||
@return [iter(Gauge)]
|
||||
:param pattern: if specified, used to search gauges
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Gauge`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,27 +18,40 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, FloatField
|
||||
|
||||
|
||||
__all__ = ['IpLocation', 'ICapGeolocIp']
|
||||
|
||||
|
||||
class IpLocation(CapBaseObject):
|
||||
"""
|
||||
Represents the location of an IP address.
|
||||
"""
|
||||
city = StringField('City')
|
||||
region = StringField('Region')
|
||||
zipcode = StringField('Zip code')
|
||||
country = StringField('Country')
|
||||
lt = FloatField('Latitude')
|
||||
lg = FloatField('Longitude')
|
||||
host = StringField('Hostname')
|
||||
tld = StringField('Top Level Domain')
|
||||
isp = StringField('Internet Service Provider')
|
||||
|
||||
def __init__(self, ipaddr):
|
||||
CapBaseObject.__init__(self, ipaddr)
|
||||
|
||||
self.ipaddr = ipaddr
|
||||
self.add_field('city', basestring)
|
||||
self.add_field('region', basestring)
|
||||
self.add_field('zipcode', basestring)
|
||||
self.add_field('country', basestring)
|
||||
self.add_field('lt', float)
|
||||
self.add_field('lg', float)
|
||||
self.add_field('host', basestring)
|
||||
self.add_field('tld', basestring)
|
||||
self.add_field('isp', basestring)
|
||||
|
||||
class ICapGeolocIp(IBaseCap):
|
||||
"""
|
||||
Access information about IP addresses database.
|
||||
"""
|
||||
def get_location(self, ipaddr):
|
||||
"""
|
||||
Get location of an IP address.
|
||||
|
||||
:param ipaddr: IP address
|
||||
:type ipaddr: str
|
||||
:rtype: :class:`IpLocation`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,19 +18,23 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import date
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, IntField, FloatField, \
|
||||
StringField, BytesField, DateField
|
||||
|
||||
|
||||
__all__ = ['ICapHousing']
|
||||
__all__ = ['HousingPhoto', 'Housing', 'Query', 'City', 'ICapHousing']
|
||||
|
||||
|
||||
class HousingPhoto(CapBaseObject):
|
||||
"""
|
||||
Photo of a housing.
|
||||
"""
|
||||
url = StringField('Direct URL to photo')
|
||||
data = BytesField('Data of photo')
|
||||
|
||||
def __init__(self, url):
|
||||
CapBaseObject.__init__(self, url.split('/')[-1])
|
||||
self.add_field('url', basestring, url)
|
||||
self.add_field('data', str)
|
||||
self.url = url
|
||||
|
||||
def __iscomplete__(self):
|
||||
return self.data
|
||||
|
|
@ -42,45 +46,75 @@ class HousingPhoto(CapBaseObject):
|
|||
return u'<HousingPhoto "%s" data=%do>' % (self.id, len(self.data) if self.data else 0)
|
||||
|
||||
class Housing(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('title', basestring)
|
||||
self.add_field('area', (int,float))
|
||||
self.add_field('cost', (int,float))
|
||||
self.add_field('currency', basestring)
|
||||
self.add_field('date', date)
|
||||
self.add_field('location', basestring)
|
||||
self.add_field('station', basestring)
|
||||
self.add_field('text', basestring)
|
||||
self.add_field('phone', basestring)
|
||||
self.add_field('photos', list)
|
||||
self.add_field('details', dict)
|
||||
"""
|
||||
Content of a housing.
|
||||
"""
|
||||
title = StringField('Title of housing')
|
||||
area = FloatField('Area of housing, in m2')
|
||||
cost = FloatField('Cost of housing')
|
||||
currency = StringField('Currency of cost')
|
||||
date = DateField('Date when the housing has been published')
|
||||
location = StringField('Location of housing')
|
||||
station = StringField('What metro/bus station next to housing')
|
||||
text = StringField('Text of the housing')
|
||||
phone = StringField('Phone number to contact')
|
||||
photos = Field('List of photos', list)
|
||||
details = Field('Key/values of details', dict)
|
||||
|
||||
class Query(CapBaseObject):
|
||||
"""
|
||||
Query to find housings.
|
||||
"""
|
||||
TYPE_RENT = 0
|
||||
TYPE_SALE = 1
|
||||
|
||||
type = IntField('Type of housing to find (TYPE_* constants)')
|
||||
cities = Field('List of cities to search in', list, tuple)
|
||||
area_min = IntField('Minimal area (in m2)')
|
||||
area_max = IntField('Maximal area (in m2)')
|
||||
cost_min = IntField('Minimal cost')
|
||||
cost_max = IntField('Maximal cost')
|
||||
nb_rooms = IntField('Number of rooms')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, '')
|
||||
self.add_field('type', int)
|
||||
self.add_field('cities', (list,tuple))
|
||||
self.add_field('area_min', int)
|
||||
self.add_field('area_max', int)
|
||||
self.add_field('cost_min', int)
|
||||
self.add_field('cost_max', int)
|
||||
self.add_field('nb_rooms', int)
|
||||
|
||||
class City(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring)
|
||||
"""
|
||||
City.
|
||||
"""
|
||||
name = StringField('Name of city')
|
||||
|
||||
class ICapHousing(IBaseCap):
|
||||
"""
|
||||
Capability of websites to search housings.
|
||||
"""
|
||||
def search_housings(self, query):
|
||||
"""
|
||||
Search housings.
|
||||
|
||||
:param query: search query
|
||||
:type query: :class:`Query`
|
||||
:rtype: iter[:class:`Housing`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_housing(self, housing):
|
||||
"""
|
||||
Get an housing from an ID.
|
||||
|
||||
:param housing: ID of the housing
|
||||
:type housing: str
|
||||
:rtype: :class:`Housing` or None if not found.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def search_city(self, pattern):
|
||||
"""
|
||||
Search a city from a pattern.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`City`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,40 +17,59 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime, date
|
||||
|
||||
from .collection import ICapCollection
|
||||
from .base import CapBaseObject
|
||||
from .base import CapBaseObject, Field, StringField, DateField
|
||||
|
||||
|
||||
__all__ = ['ICapBook', 'Book']
|
||||
__all__ = ['Book', 'Renew', 'ICapBook']
|
||||
|
||||
|
||||
class Book(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('location', basestring)
|
||||
self.add_field('date', (datetime, date)) # which may be the due date
|
||||
self.add_field('late', bool)
|
||||
"""
|
||||
Describes a book.
|
||||
"""
|
||||
name = StringField('Name of the book')
|
||||
author = StringField('Author of the book')
|
||||
location = StringField('Location')
|
||||
date = DateField('The due date')
|
||||
late = Field('Are you late?', bool)
|
||||
|
||||
class Renew(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('message', basestring)
|
||||
"""
|
||||
A renew message.
|
||||
"""
|
||||
message = StringField('Message')
|
||||
|
||||
class ICapBook(ICapCollection):
|
||||
"""
|
||||
Library websites.
|
||||
"""
|
||||
def iter_resources(self, objs, split_path):
|
||||
"""
|
||||
Iter resources. It retuns :func:`iter_books`.
|
||||
"""
|
||||
if Book in objs:
|
||||
self._restrict_level(split_path)
|
||||
|
||||
return self.iter_books()
|
||||
|
||||
def iter_books(self, pattern):
|
||||
"""
|
||||
Iter books from a pattern.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Book`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_book(self, _id):
|
||||
"""
|
||||
Get a book from an ID.
|
||||
|
||||
:param _id: ID of the book
|
||||
:type _id: str
|
||||
:rtype: :class:`Book`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_booked(self, _id):
|
||||
|
|
|
|||
|
|
@ -21,18 +21,42 @@
|
|||
import datetime
|
||||
import time
|
||||
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded, Field, StringField, DateField, IntField
|
||||
|
||||
|
||||
__all__ = ['ICapMessages', 'ICapMessagesPost', 'Message', 'Thread', 'CantSendMessage']
|
||||
__all__ = ['Thread', 'Message', 'ICapMessages', 'CantSendMessage', 'ICapMessagesPost']
|
||||
|
||||
|
||||
class Message(CapBaseObject):
|
||||
# Message and Thread's attributes refer to themselves, and it isn't possible
|
||||
# in python, so these base classes are used instead.
|
||||
class _Message(CapBaseObject):
|
||||
""" Base message. """
|
||||
pass
|
||||
|
||||
class _Thread(CapBaseObject):
|
||||
""" Base Thread. """
|
||||
pass
|
||||
|
||||
class Message(_Message):
|
||||
"""
|
||||
Represents a message read or to send.
|
||||
"""
|
||||
IS_HTML = 0x001 # The content is HTML formatted
|
||||
IS_UNREAD = 0x002 # The message is unread
|
||||
IS_RECEIVED = 0x004 # The receiver has read this message
|
||||
IS_NOT_RECEIVED = 0x008 # The receiver has not read this message
|
||||
|
||||
thread = Field('Reference to the thread', _Thread)
|
||||
title = StringField('Title of message')
|
||||
sender = StringField('Author of this message')
|
||||
receivers = Field('Receivers of the message', list)
|
||||
date = DateField('Date when the message has been sent')
|
||||
content = StringField('Body of message')
|
||||
signature = StringField('Optional signature')
|
||||
parent = Field('Parent message', _Message)
|
||||
children = Field('Children fields', list)
|
||||
flags = IntField('Flags (IS_* constants)', default=0)
|
||||
|
||||
def __init__(self, thread, id,
|
||||
title=NotLoaded,
|
||||
sender=NotLoaded,
|
||||
|
|
@ -45,16 +69,14 @@ class Message(CapBaseObject):
|
|||
flags=0):
|
||||
CapBaseObject.__init__(self, id)
|
||||
assert thread is not None
|
||||
self.add_field('thread', Thread, thread)
|
||||
self.add_field('title', basestring, title)
|
||||
self.add_field('sender', basestring, sender)
|
||||
self.add_field('receivers', list, receivers)
|
||||
self.add_field('date', (datetime.datetime, datetime.date), date)
|
||||
self.add_field('parent', Message, parent)
|
||||
self.add_field('content', basestring, content)
|
||||
self.add_field('signature', basestring, signature)
|
||||
self.add_field('children', list, children)
|
||||
self.add_field('flags', int, flags)
|
||||
self.thread = thread
|
||||
self.title = title
|
||||
self.sender = sender
|
||||
self.receivers = receivers
|
||||
self.content = content
|
||||
self.signature = signature
|
||||
self.children = children
|
||||
self.flags = flags
|
||||
|
||||
if date is None:
|
||||
date = datetime.datetime.utcnow()
|
||||
|
|
@ -68,14 +90,23 @@ class Message(CapBaseObject):
|
|||
|
||||
@property
|
||||
def date_int(self):
|
||||
"""
|
||||
Date of message as an integer.
|
||||
"""
|
||||
return int(time.strftime('%Y%m%d%H%M%S', self.date.timetuple()))
|
||||
|
||||
@property
|
||||
def full_id(self):
|
||||
"""
|
||||
Full ID of message (in form '**THREAD_ID.MESSAGE_ID**')
|
||||
"""
|
||||
return '%s.%s' % (self.thread.id, self.id)
|
||||
|
||||
@property
|
||||
def full_parent_id(self):
|
||||
"""
|
||||
Get the full ID of the parent message (in form '**THREAD_ID.MESSAGE_ID**').
|
||||
"""
|
||||
if self.parent:
|
||||
return self.parent.full_id
|
||||
elif self._parent_id is None:
|
||||
|
|
@ -96,18 +127,24 @@ class Message(CapBaseObject):
|
|||
return '<Message id=%r title=%r date=%r from=%r>' % (
|
||||
self.full_id, self.title, self.date, self.sender)
|
||||
|
||||
class Thread(CapBaseObject):
|
||||
class Thread(_Thread):
|
||||
"""
|
||||
Thread containing messages.
|
||||
"""
|
||||
IS_THREADS = 0x001
|
||||
IS_DISCUSSION = 0x002
|
||||
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('root', Message)
|
||||
self.add_field('title', basestring)
|
||||
self.add_field('date', (datetime.datetime, datetime.date))
|
||||
self.add_field('flags', int, self.IS_THREADS)
|
||||
root = Field('Root message', Message)
|
||||
title = StringField('Title of thread')
|
||||
date = DateField('Date of thread')
|
||||
flags = IntField('Flags (IS_* constants)', default=0)
|
||||
|
||||
def iter_all_messages(self):
|
||||
"""
|
||||
Iter all messages of the thread.
|
||||
|
||||
:rtype: iter[:class:`Message`]
|
||||
"""
|
||||
if self.root:
|
||||
yield self.root
|
||||
for m in self._iter_all_messages(self.root):
|
||||
|
|
@ -122,11 +159,14 @@ class Thread(CapBaseObject):
|
|||
|
||||
|
||||
class ICapMessages(IBaseCap):
|
||||
"""
|
||||
Capability to read messages.
|
||||
"""
|
||||
def iter_threads(self):
|
||||
"""
|
||||
Iterates on threads, from newers to olders.
|
||||
|
||||
@return [iter] Thread objects
|
||||
:rtype: iter[:class:`Thread`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -134,7 +174,7 @@ class ICapMessages(IBaseCap):
|
|||
"""
|
||||
Get a specific thread.
|
||||
|
||||
@return [Thread] the Thread object
|
||||
:rtype: :class:`Thread`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -142,7 +182,7 @@ class ICapMessages(IBaseCap):
|
|||
"""
|
||||
Iterates on messages which hasn't been marked as read.
|
||||
|
||||
@return [iter] Message objects
|
||||
:rtype: iter[:class:`Message`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -150,19 +190,26 @@ class ICapMessages(IBaseCap):
|
|||
"""
|
||||
Set a message as read.
|
||||
|
||||
@param [message] message read (or ID)
|
||||
:param message: message read (or ID)
|
||||
:type message: :class:`Message` or str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
class CantSendMessage(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when a message can't be send.
|
||||
"""
|
||||
|
||||
class ICapMessagesPost(IBaseCap):
|
||||
"""
|
||||
This capability allow user to send a message.
|
||||
"""
|
||||
def post_message(self, message):
|
||||
"""
|
||||
Post a message.
|
||||
|
||||
@param message Message object
|
||||
@return
|
||||
:param message: message to send
|
||||
:type message: :class:`Message`
|
||||
:raises: :class:`CantSendMessage`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,27 +18,34 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded
|
||||
from .base import IBaseCap, CapBaseObject, NotLoaded, Field, StringField
|
||||
|
||||
|
||||
__all__ = ['PasteNotFound', 'BasePaste', 'ICapPaste']
|
||||
|
||||
|
||||
class PasteNotFound(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when a paste is not found.
|
||||
"""
|
||||
|
||||
class BasePaste(CapBaseObject):
|
||||
"""
|
||||
Represents a pasted text.
|
||||
"""
|
||||
title = StringField('Title of paste')
|
||||
language = StringField('Language of the paste')
|
||||
contents = StringField('Content of the paste')
|
||||
public = Field('Is this paste public?', bool)
|
||||
|
||||
def __init__(self, _id, title=NotLoaded, language=NotLoaded, contents=NotLoaded,
|
||||
public=NotLoaded):
|
||||
CapBaseObject.__init__(self, unicode(_id))
|
||||
|
||||
self.add_field('title', basestring, title)
|
||||
self.add_field('language', basestring, language)
|
||||
self.add_field('contents', basestring, contents)
|
||||
self.add_field('public', bool, public)
|
||||
self.title = title
|
||||
self.language = language
|
||||
self.contents = contents
|
||||
self.public = public
|
||||
|
||||
@classmethod
|
||||
def id2url(cls, _id):
|
||||
|
|
@ -47,6 +54,9 @@ class BasePaste(CapBaseObject):
|
|||
|
||||
@property
|
||||
def page_url(self):
|
||||
"""
|
||||
Get URL to page of this paste.
|
||||
"""
|
||||
return self.id2url(self.id)
|
||||
|
||||
|
||||
|
|
@ -60,7 +70,7 @@ class ICapPaste(IBaseCap):
|
|||
Get a new paste object for posting it with the backend.
|
||||
The parameters should be passed to the object init.
|
||||
|
||||
@return a Paste object
|
||||
:rtype: :class:`BasePaste`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -79,7 +89,8 @@ class ICapPaste(IBaseCap):
|
|||
A score of 1 means the backend is suitable.
|
||||
Higher scores means it is more suitable than others with a lower score.
|
||||
|
||||
@return int Score
|
||||
:rtype: int
|
||||
:returns: score
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -87,8 +98,10 @@ class ICapPaste(IBaseCap):
|
|||
"""
|
||||
Get a Paste from an ID or URL.
|
||||
|
||||
@param _id the paste id. It can be an ID or a page URL.
|
||||
@return a Paste object
|
||||
:param _id: the paste id. It can be an ID or a page URL.
|
||||
:type _id: str
|
||||
:rtype: :class:`BasePaste`
|
||||
:raises: :class:`PasteNotFound`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -96,7 +109,7 @@ class ICapPaste(IBaseCap):
|
|||
"""
|
||||
Post a paste.
|
||||
|
||||
@param paste Paste object
|
||||
@return
|
||||
:param paste: a Paste object
|
||||
:type paste: :class:`BasePaste`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,17 +18,18 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField
|
||||
|
||||
|
||||
__all__ = ['Emission', 'Stream', 'Radio', 'ICapRadio']
|
||||
|
||||
|
||||
class Emission(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('artist', unicode)
|
||||
self.add_field('title', unicode)
|
||||
"""
|
||||
Emission of a radio.
|
||||
"""
|
||||
artist = StringField('Name of artist')
|
||||
title = StringField('Title of song or emission')
|
||||
|
||||
def __iscomplete__(self):
|
||||
# This volatile information may be reloaded everytimes.
|
||||
|
|
@ -41,10 +42,11 @@ class Emission(CapBaseObject):
|
|||
return self.title
|
||||
|
||||
class Stream(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('title', unicode)
|
||||
self.add_field('url', unicode)
|
||||
"""
|
||||
Stream of a radio.
|
||||
"""
|
||||
title = StringField('Title of stream')
|
||||
url = StringField('Direct URL to the stream')
|
||||
|
||||
def __unicode__(self):
|
||||
return u'%s (%s)' % (self.title, self.url)
|
||||
|
|
@ -53,16 +55,34 @@ class Stream(CapBaseObject):
|
|||
return self.__unicode__()
|
||||
|
||||
class Radio(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('title', unicode)
|
||||
self.add_field('description', unicode)
|
||||
self.add_field('current', Emission)
|
||||
self.add_field('streams', list)
|
||||
"""
|
||||
Radio object.
|
||||
"""
|
||||
title = StringField('Title of radio')
|
||||
description = StringField('Description of radio')
|
||||
current = Field('Current emission', Emission)
|
||||
streams = Field('List of streams', list)
|
||||
|
||||
class ICapRadio(IBaseCap):
|
||||
"""
|
||||
Capability of radio websites.
|
||||
"""
|
||||
def iter_radios_search(self, pattern):
|
||||
"""
|
||||
Search a radio.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Radio`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_radio(self, id):
|
||||
"""
|
||||
Get a radio from an ID.
|
||||
|
||||
:param id: ID of radio
|
||||
:type id: str
|
||||
:rtype: :class:`Radio`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -17,41 +17,72 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, StringField, FloatField, DateField, IntField
|
||||
|
||||
|
||||
__all__ = ['ICapTorrent', 'Torrent']
|
||||
__all__ = ['MagnetOnly', 'Torrent', 'ICapTorrent']
|
||||
|
||||
|
||||
class MagnetOnly(Exception):
|
||||
"""
|
||||
Raised when trying to get URL to torrent but only magnet is available.
|
||||
"""
|
||||
def __init__(self, magnet):
|
||||
self.magnet = magnet
|
||||
Exception.__init__(self, 'Only magnet URL is available')
|
||||
|
||||
|
||||
class Torrent(CapBaseObject):
|
||||
"""
|
||||
Torrent object.
|
||||
"""
|
||||
name = StringField('Name of torrent')
|
||||
size = FloatField('Size of torrent')
|
||||
date = DateField('Date when torrent has been published')
|
||||
url = StringField('Direct url to .torrent file')
|
||||
magnet = StringField('URI of magnet')
|
||||
seeders = IntField('Number of seeders')
|
||||
leechers = IntField('Number of leechers')
|
||||
files = Field('Files in torrent', list)
|
||||
description = StringField('Description of torrent')
|
||||
filename = StringField('Name of .torrent file')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring, name)
|
||||
self.add_field('size', (int, long, float))
|
||||
self.add_field('date', datetime)
|
||||
self.add_field('url', basestring)
|
||||
self.add_field('magnet', basestring)
|
||||
self.add_field('seeders', int)
|
||||
self.add_field('leechers', int)
|
||||
self.add_field('files', list)
|
||||
self.add_field('description', basestring)
|
||||
self.add_field('filename', basestring) # suggested name of the .torrent file
|
||||
self.name = name
|
||||
|
||||
|
||||
class ICapTorrent(IBaseCap):
|
||||
"""
|
||||
Torrent trackers.
|
||||
"""
|
||||
def iter_torrents(self, pattern):
|
||||
"""
|
||||
Search torrents and iterate on results.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Torrent`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_torrent(self, _id):
|
||||
"""
|
||||
Get a torrent object from an ID.
|
||||
|
||||
:param _id: ID of torrent
|
||||
:type _id: str
|
||||
:rtype: :class:`Torrent`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_torrent_file(self, _id):
|
||||
"""
|
||||
Get the content of the .torrent file.
|
||||
|
||||
:param _id: ID of torrent
|
||||
:type _id: str
|
||||
:rtype: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -18,76 +18,98 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import time, datetime, timedelta
|
||||
import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, StringField, TimeField, DeltaField, DateField
|
||||
|
||||
|
||||
__all__ = ['Departure', 'ICapTravel', 'Station']
|
||||
__all__ = ['Station', 'Departure', 'RoadStep', 'RoadmapError', 'RoadmapFilters', 'ICapTravel']
|
||||
|
||||
|
||||
class Station(CapBaseObject):
|
||||
"""
|
||||
Describes a station.
|
||||
"""
|
||||
name = StringField('Name of station')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring, name)
|
||||
self.name = name
|
||||
|
||||
def __repr__(self):
|
||||
return "<Station id=%r name=%r>" % (self.id, self.name)
|
||||
|
||||
class Departure(CapBaseObject):
|
||||
"""
|
||||
Describes a departure.
|
||||
"""
|
||||
type = StringField('Type of train')
|
||||
time = TimeField('When the train will leave')
|
||||
departure_station = StringField('Departure station')
|
||||
arrival_station = StringField('Destination of the train')
|
||||
late = TimeField('Optional late', default=datetime.time())
|
||||
information = StringField('Informations')
|
||||
plateform = StringField('Where the train will leave')
|
||||
|
||||
def __init__(self, id, _type, _time):
|
||||
CapBaseObject.__init__(self, id)
|
||||
|
||||
self.add_field('type', basestring, _type)
|
||||
self.add_field('time', datetime, _time)
|
||||
self.add_field('departure_station', basestring)
|
||||
self.add_field('arrival_station', basestring)
|
||||
self.add_field('late', time, time())
|
||||
self.add_field('information', basestring)
|
||||
self.add_field('plateform', basestring)
|
||||
self.type = _type
|
||||
self.time = _time
|
||||
|
||||
def __repr__(self):
|
||||
return u"<Departure id=%r type=%r time=%r departure=%r arrival=%r>" % (
|
||||
self.id, self.type, self.time.strftime('%H:%M'), self.departure_station, self.arrival_station)
|
||||
|
||||
class RoadStep(CapBaseObject):
|
||||
def __init__(self, id):
|
||||
CapBaseObject.__init__(self, id)
|
||||
|
||||
self.add_field('line', basestring)
|
||||
self.add_field('start_time', time)
|
||||
self.add_field('end_time', time)
|
||||
self.add_field('departure', unicode)
|
||||
self.add_field('arrival', unicode)
|
||||
self.add_field('duration', timedelta)
|
||||
"""
|
||||
A step on a roadmap.
|
||||
"""
|
||||
line = StringField('When line')
|
||||
start_time = TimeField('Start of step')
|
||||
end_time = TimeField('End of step')
|
||||
departure = StringField('Departure station')
|
||||
arrival = StringField('Arrival station')
|
||||
duration = DeltaField('Duration of this step')
|
||||
|
||||
class RoadmapError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when the roadmap is unable to be calculated.
|
||||
"""
|
||||
|
||||
class RoadmapFilters(CapBaseObject):
|
||||
"""
|
||||
Filters to get a roadmap.
|
||||
"""
|
||||
departure_time = DateField('Wanted departure time')
|
||||
arrival_time = DateField('Wanted arrival time')
|
||||
|
||||
def __init__(self):
|
||||
CapBaseObject.__init__(self, '')
|
||||
|
||||
self.add_field('departure_time', datetime)
|
||||
self.add_field('arrival_time', datetime)
|
||||
|
||||
class ICapTravel(IBaseCap):
|
||||
"""
|
||||
Travel websites.
|
||||
"""
|
||||
def iter_station_search(self, pattern):
|
||||
"""
|
||||
Iterates on search results of stations.
|
||||
|
||||
@param pattern [str] the search pattern
|
||||
@return [iter] the of Station objects
|
||||
:param pattern: the search pattern
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`Station`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_station_departures(self, station_id, arrival_id):
|
||||
def iter_station_departures(self, station_id, arrival_id=None):
|
||||
"""
|
||||
Iterate on departures.
|
||||
|
||||
@param station_id [id] the station id
|
||||
@param arrival_id [id] optionnal arrival station id
|
||||
@return [iter] result of Departure objects
|
||||
:param station_id: the station ID
|
||||
:type station_id: str
|
||||
:param arrival_id: optionnal arrival station ID
|
||||
:type arrival_id: str
|
||||
:rtype: iter[:class:`Departure`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -95,9 +117,12 @@ class ICapTravel(IBaseCap):
|
|||
"""
|
||||
Get a roadmap.
|
||||
|
||||
@param departure [str] name of departure station
|
||||
@param arrival [str] name of arrival station
|
||||
@param filters [RoadmapFilters] filters on search
|
||||
@return [iter(RoadStep)] steps of roadmap
|
||||
:param departure: name of departure station
|
||||
:type departure: str
|
||||
:param arrival: name of arrival station
|
||||
:type arrival: str
|
||||
:param filters: filters on search
|
||||
:type filters: :class:`RoadmapFilters`
|
||||
:rtype: iter[:class:`RoadStep`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2010-2011 Romain Bignon, Christophe Benz
|
||||
# Copyright(C) 2010-2012 Romain Bignon, Christophe Benz
|
||||
#
|
||||
# This file is part of weboob.
|
||||
#
|
||||
|
|
@ -18,34 +18,33 @@
|
|||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
|
||||
from .base import IBaseCap, CapBaseObject, NotAvailable
|
||||
from .base import IBaseCap, CapBaseObject, NotAvailable, StringField, Field, DateField
|
||||
from weboob.tools.capabilities.thumbnail import Thumbnail
|
||||
|
||||
|
||||
__all__ = ['BaseVideo', 'ICapVideo']
|
||||
|
||||
|
||||
class BaseVideo(CapBaseObject):
|
||||
"""
|
||||
Represents a video.
|
||||
|
||||
This object has to be inherited to specify how to calculate the URL of the video from its ID.
|
||||
"""
|
||||
|
||||
def __init__(self, _id):
|
||||
CapBaseObject.__init__(self, unicode(_id))
|
||||
|
||||
self.add_field('title', basestring)
|
||||
self.add_field('url', basestring)
|
||||
self.add_field('ext', basestring)
|
||||
self.add_field('author', basestring)
|
||||
self.add_field('description', basestring)
|
||||
self.add_field('duration', (int,long,timedelta))
|
||||
self.add_field('date', datetime)
|
||||
self.add_field('rating', (int,long,float), NotAvailable)
|
||||
self.add_field('rating_max', (int,long,float), NotAvailable)
|
||||
self.add_field('thumbnail', Thumbnail)
|
||||
self.add_field('nsfw', bool, False)
|
||||
title = StringField('Title of video')
|
||||
url = StringField('URL to the video file')
|
||||
ext = StringField('Extension of video')
|
||||
author = StringField('Author of video')
|
||||
description = StringField('Description of video')
|
||||
duration = Field('Duration of video', int, long, timedelta)
|
||||
date = DateField('Date when the video has been published')
|
||||
rating = Field('Rating of video', int, long, float, default=NotAvailable)
|
||||
rating_max = Field('Max rating', int, long, float, default=NotAvailable)
|
||||
thumbnail = Field('Thumbnail of video', Thumbnail)
|
||||
nsfw = Field('Is this video Not Safe For Work', bool, default=False)
|
||||
|
||||
@classmethod
|
||||
def id2url(cls, _id):
|
||||
|
|
@ -54,6 +53,9 @@ class BaseVideo(CapBaseObject):
|
|||
|
||||
@property
|
||||
def page_url(self):
|
||||
"""
|
||||
Get page URL of the video.
|
||||
"""
|
||||
return self.id2url(self.id)
|
||||
|
||||
|
||||
|
|
@ -70,10 +72,14 @@ class ICapVideo(IBaseCap):
|
|||
"""
|
||||
Iter results of a search on a pattern.
|
||||
|
||||
@param pattern [str] pattern to search on
|
||||
@param sortby [enum] sort by...
|
||||
@param nsfw [bool] include non-suitable for work videos if True
|
||||
@param max_results [int] maximum number of results to return
|
||||
:param pattern: pattern to search on
|
||||
:type pattern: str
|
||||
:param sortby: sort by... (use SEARCH_* constants)
|
||||
:param nsfw: include non-suitable for work videos if True
|
||||
:type nsfw: bool
|
||||
:param max_results: maximum number of results to return
|
||||
:type max_results: int
|
||||
:rtype: iter[:class:`BaseVideo`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -81,7 +87,8 @@ class ICapVideo(IBaseCap):
|
|||
"""
|
||||
Get a Video from an ID.
|
||||
|
||||
@param _id the video id. It can be a numeric ID, or a page url, or so.
|
||||
@return a Video object
|
||||
:param _id: the video id. It can be a numeric ID, or a page url
|
||||
:type _id: str
|
||||
:rtype: :class:`BaseVideo` or None is fot found.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -20,43 +20,89 @@
|
|||
|
||||
from datetime import datetime
|
||||
|
||||
from .base import IBaseCap, CapBaseObject
|
||||
from .base import IBaseCap, CapBaseObject, Field, DateField, FloatField, StringField
|
||||
|
||||
|
||||
__all__ = ['City', 'CityNotFound', 'Current', 'Forecast', 'ICapWeather']
|
||||
__all__ = ['Forecast', 'Current', 'City', 'CityNotFound', 'ICapWeather']
|
||||
|
||||
|
||||
class Forecast(CapBaseObject):
|
||||
"""
|
||||
Weather forecast.
|
||||
"""
|
||||
date = Field('Date for the forecast', datetime, basestring)
|
||||
low = FloatField('Low temperature')
|
||||
high = FloatField('High temperature')
|
||||
text = StringField('Comment on forecast')
|
||||
unit = StringField('Unit used for temperatures')
|
||||
|
||||
def __init__(self, date, low, high, text, unit):
|
||||
CapBaseObject.__init__(self, date)
|
||||
self.add_field('date', (basestring,datetime), date)
|
||||
self.add_field('low', (int,float), low)
|
||||
self.add_field('high', (int,float), high)
|
||||
self.add_field('text', basestring, text)
|
||||
self.add_field('unit', basestring, unit)
|
||||
CapBaseObject.__init__(self, unicode(date))
|
||||
self.date = date
|
||||
self.low = low
|
||||
self.high = high
|
||||
self.text = text
|
||||
self.unit = unit
|
||||
|
||||
class Current(CapBaseObject):
|
||||
"""
|
||||
Current weather.
|
||||
"""
|
||||
date = DateField('Date of measure')
|
||||
text = StringField('Comment about current weather')
|
||||
temp = FloatField('Current temperature')
|
||||
unit = StringField('Unit used for temperature')
|
||||
|
||||
def __init__(self, date, temp, text, unit):
|
||||
CapBaseObject.__init__(self, date)
|
||||
self.add_field('date', (basestring,datetime), date)
|
||||
self.add_field('text', basestring, text)
|
||||
self.add_field('temp', (int,float), temp)
|
||||
self.add_field('unit', basestring, unit)
|
||||
CapBaseObject.__init__(self, unicode(date))
|
||||
self.date = date
|
||||
self.text = text
|
||||
self.temp = temp
|
||||
self.unit = unit
|
||||
|
||||
class City(CapBaseObject):
|
||||
"""
|
||||
City where to find weather.
|
||||
"""
|
||||
name = StringField('Name of city')
|
||||
|
||||
def __init__(self, id, name):
|
||||
CapBaseObject.__init__(self, id)
|
||||
self.add_field('name', basestring, name)
|
||||
self.name = name
|
||||
|
||||
class CityNotFound(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when a city is not found.
|
||||
"""
|
||||
|
||||
class ICapWeather(IBaseCap):
|
||||
"""
|
||||
Capability for weather websites.
|
||||
"""
|
||||
def iter_city_search(self, pattern):
|
||||
"""
|
||||
Look for a city.
|
||||
|
||||
:param pattern: pattern to search
|
||||
:type pattern: str
|
||||
:rtype: iter[:class:`City`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_current(self, city_id):
|
||||
"""
|
||||
Get current weather.
|
||||
|
||||
:param city_id: ID of the city
|
||||
:rtype: :class:`Current`
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def iter_forecast(self, city_id):
|
||||
"""
|
||||
Iter forecasts of a city.
|
||||
|
||||
:param city_id: ID of the city
|
||||
:rtype: iter[:class:`Forecast`]
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,19 @@ __all__ = ['Weboob']
|
|||
|
||||
|
||||
class Weboob(object):
|
||||
"""
|
||||
The main class of Weboob, used to manage backends and call methods.
|
||||
|
||||
:param workdir: optional parameter to set path of the working directory
|
||||
:type workdir: str
|
||||
:param backends_filename: name of the *backends* file, where configuration of
|
||||
backends is stored
|
||||
:type backends_filename: str
|
||||
:param scheduler: what scheduler to use; default is :class:`weboob.core.scheduler.Scheduler`
|
||||
:type scheduler: :class:`weboob.core.scheduler.IScheduler`
|
||||
:param storage: provide a storage where backends can save data
|
||||
:type storage: :class:`weboob.tools.storage.IStorage`
|
||||
"""
|
||||
VERSION = '0.c'
|
||||
BACKENDS_FILENAME = 'backends'
|
||||
|
||||
|
|
@ -64,8 +77,8 @@ class Weboob(object):
|
|||
if os.path.isdir(old_workdir):
|
||||
self.logger.warning('You are using "%s" as working directory. Files are moved into %s and %s.'
|
||||
% (old_workdir, xdg_config_home, xdg_data_home))
|
||||
self.create_dir(xdg_config_home)
|
||||
self.create_dir(xdg_data_home)
|
||||
self._create_dir(xdg_config_home)
|
||||
self._create_dir(xdg_data_home)
|
||||
for f in os.listdir(old_workdir):
|
||||
if f in Repositories.SHARE_DIRS:
|
||||
dest = xdg_data_home
|
||||
|
|
@ -77,7 +90,7 @@ class Weboob(object):
|
|||
datadir = xdg_data_home
|
||||
|
||||
self.workdir = os.path.realpath(workdir)
|
||||
self.create_dir(workdir)
|
||||
self._create_dir(workdir)
|
||||
|
||||
# Repositories management
|
||||
self.repositories = Repositories(workdir, datadir, self.VERSION)
|
||||
|
|
@ -95,7 +108,7 @@ class Weboob(object):
|
|||
# Storage
|
||||
self.storage = storage
|
||||
|
||||
def create_dir(self, name):
|
||||
def _create_dir(self, name):
|
||||
if not os.path.exists(name):
|
||||
os.makedirs(name)
|
||||
elif not os.path.isdir(name):
|
||||
|
|
@ -105,11 +118,15 @@ class Weboob(object):
|
|||
self.deinit()
|
||||
|
||||
def deinit(self):
|
||||
"""
|
||||
Call this method when you stop using Weboob, to
|
||||
properly unload all correctly.
|
||||
"""
|
||||
self.unload_backends()
|
||||
|
||||
def update(self, progress=IProgress()):
|
||||
"""
|
||||
Update modules.
|
||||
Update modules from repositories.
|
||||
"""
|
||||
self.repositories.update(progress)
|
||||
|
||||
|
|
@ -120,13 +137,27 @@ class Weboob(object):
|
|||
self.repositories.install(minfo, progress)
|
||||
|
||||
class LoadError(Exception):
|
||||
"""
|
||||
Raised when a backend is unabled to load.
|
||||
|
||||
:param backend_name: name of backend we can't load
|
||||
:param exception: exception object
|
||||
"""
|
||||
def __init__(self, backend_name, exception):
|
||||
Exception.__init__(self, unicode(exception))
|
||||
self.backend_name = backend_name
|
||||
|
||||
def load_backend(self, module_name, params=None, storage=None):
|
||||
def build_backend(self, module_name, params=None, storage=None):
|
||||
"""
|
||||
Load a single backend.
|
||||
Create a single backend which is not listed
|
||||
in configuration.
|
||||
|
||||
:param module_name: name of module
|
||||
:param params: parameters to give to backend
|
||||
:type params: :class:`dict`
|
||||
:param storage: storage to use
|
||||
:type storage: :class:`weboob.tools.storage.IStorage`
|
||||
:rtype: :class:`weboob.tools.backend.BaseBackend`
|
||||
"""
|
||||
minfo = self.repositories.get_module_info(module_name)
|
||||
if minfo is None:
|
||||
|
|
@ -143,14 +174,20 @@ class Weboob(object):
|
|||
|
||||
def load_backends(self, caps=None, names=None, modules=None, storage=None, errors=None):
|
||||
"""
|
||||
Load backends.
|
||||
Load backends listed in config file.
|
||||
|
||||
@param caps [tuple(ICapBase)] load backends which implement all of caps
|
||||
@param names [tuple(unicode)] load backends with instance name in list
|
||||
@param modules [tuple(unicode)] load backends which module is in list
|
||||
@param storage [IStorage] use the storage if specified
|
||||
@param errors [list] if specified, store every errors in
|
||||
@return [dict(str,BaseBackend)] return loaded backends
|
||||
:param caps: load backends which implement all of specified caps
|
||||
:type caps: tuple[:class:`weboob.capabilities.base.ICapBase`]
|
||||
:param names: load backends with instance name in list
|
||||
:type names: tuple[:class:`str`]
|
||||
:param modules: load backends which module is in list
|
||||
:type modules: tuple[:class:`str`]
|
||||
:param storage: use this storage if specified
|
||||
:type storage: :class:`weboob.tools.storage.IStorage`
|
||||
:param errors: if specified, store every errors in this list
|
||||
:type errors: list[:class:`LoadError`]
|
||||
:returns: loaded backends
|
||||
:rtype: dict[:class:`str`, :class:`weboob.tools.backend.BaseBackend`]
|
||||
"""
|
||||
loaded = {}
|
||||
if storage is None:
|
||||
|
|
@ -195,6 +232,12 @@ class Weboob(object):
|
|||
return loaded
|
||||
|
||||
def unload_backends(self, names=None):
|
||||
"""
|
||||
Unload backends.
|
||||
|
||||
:param names: if specified, only unload that backends
|
||||
:type names: :class:`list`
|
||||
"""
|
||||
unloaded = {}
|
||||
if isinstance(names, basestring):
|
||||
names = [names]
|
||||
|
|
@ -213,8 +256,11 @@ class Weboob(object):
|
|||
"""
|
||||
Get a backend from its name.
|
||||
|
||||
It raises a KeyError if not found. If you set the 'default' parameter,
|
||||
the default value is returned instead.
|
||||
:param name: name of backend to get
|
||||
:type name: str
|
||||
:param default: if specified, get this value when the backend is not found
|
||||
:type default: whatever you want
|
||||
:raises: :class:`KeyError` if not found.
|
||||
"""
|
||||
try:
|
||||
return self.backend_instances[name]
|
||||
|
|
@ -225,6 +271,9 @@ class Weboob(object):
|
|||
raise
|
||||
|
||||
def count_backends(self):
|
||||
"""
|
||||
Get number of loaded backends.
|
||||
"""
|
||||
return len(self.backend_instances)
|
||||
|
||||
def iter_backends(self, caps=None):
|
||||
|
|
@ -233,8 +282,9 @@ class Weboob(object):
|
|||
|
||||
Note: each backend is locked when it is returned.
|
||||
|
||||
@param caps Optional list of capabilities to select backends
|
||||
@return iterator on selected backends.
|
||||
:param caps: optional list of capabilities to select backends
|
||||
:type caps: tuple[:class:`weboob.capabilities.base.IBaseCap`]
|
||||
:rtype: iter[:class:`weboob.tools.backend.BaseBackend`]
|
||||
"""
|
||||
for name, backend in sorted(self.backend_instances.iteritems()):
|
||||
if caps is None or backend.has_caps(caps):
|
||||
|
|
@ -248,17 +298,21 @@ class Weboob(object):
|
|||
|
||||
This function has two modes:
|
||||
|
||||
- If 'function' is a string, it calls the method with this name on
|
||||
- If *function* is a string, it calls the method with this name on
|
||||
each backends with the specified arguments;
|
||||
- If 'function' is a callable, it calls it in a separated thread with
|
||||
- If *function* is a callable, it calls it in a separated thread with
|
||||
the locked backend instance at first arguments, and \*args and
|
||||
\*\*kwargs.
|
||||
|
||||
@param function backend's method name, or callable object
|
||||
@param backends list of backends to iterate on
|
||||
@param caps iterate on backends with this caps
|
||||
@param condition a condition to validate to keep the result
|
||||
@return the BackendsCall object (iterable)
|
||||
:param function: backend's method name, or a callable object
|
||||
:type function: :class:`str`
|
||||
:param backends: list of backends to iterate on
|
||||
:type backends: list[:class:`str`]
|
||||
:param caps: iterate on backends which implement this caps
|
||||
:type caps: list[:class:`weboob.capabilities.base.IBaseCap`]
|
||||
:param condition: a condition to validate results
|
||||
:type condition: :class:`weboob.core.bcall.IResultsCondition`
|
||||
:rtype: A :class:`weboob.core.bcall.BackendsCall` object (iterable)
|
||||
"""
|
||||
backends = self.backend_instances.values()
|
||||
_backends = kwargs.pop('backends', None)
|
||||
|
|
@ -296,16 +350,47 @@ class Weboob(object):
|
|||
return BackendsCall(backends, condition, function, *args, **kwargs)
|
||||
|
||||
def schedule(self, interval, function, *args):
|
||||
"""
|
||||
Schedule an event.
|
||||
|
||||
:param interval: delay before calling the function
|
||||
:type interval: int
|
||||
:param function: function to call
|
||||
:type function: callabale
|
||||
:param args: arguments to give to function
|
||||
:returns: an event identificator
|
||||
"""
|
||||
return self.scheduler.schedule(interval, function, *args)
|
||||
|
||||
def repeat(self, interval, function, *args):
|
||||
"""
|
||||
Repeat a call to a function
|
||||
|
||||
:param interval: interval between two calls
|
||||
:type interval: int
|
||||
:param function: function to call
|
||||
:type function: callable
|
||||
:param args: arguments to give to function
|
||||
:returns: an event identificator
|
||||
"""
|
||||
return self.scheduler.repeat(interval, function, *args)
|
||||
|
||||
def cancel(self, ev):
|
||||
"""
|
||||
Cancel an event
|
||||
|
||||
:param ev: the event identificator
|
||||
"""
|
||||
return self.scheduler.cancel(ev)
|
||||
|
||||
def want_stop(self):
|
||||
"""
|
||||
Plan to stop the scheduler.
|
||||
"""
|
||||
return self.scheduler.want_stop()
|
||||
|
||||
def loop(self):
|
||||
"""
|
||||
Run the scheduler loop
|
||||
"""
|
||||
return self.scheduler.run()
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ from .formatters.load import FormattersLoader, FormatterLoadError
|
|||
from .results import ResultsCondition, ResultsConditionError
|
||||
|
||||
|
||||
__all__ = ['ReplApplication', 'NotEnoughArguments']
|
||||
__all__ = ['NotEnoughArguments', 'ReplApplication']
|
||||
|
||||
|
||||
class NotEnoughArguments(Exception):
|
||||
|
|
@ -354,8 +354,9 @@ class ReplApplication(Cmd, ConsoleApplication):
|
|||
def complete(self, text, state):
|
||||
"""
|
||||
Override of the Cmd.complete() method to:
|
||||
- add a space at end of proposals
|
||||
- display only proposals for words which match the
|
||||
|
||||
* add a space at end of proposals
|
||||
* display only proposals for words which match the
|
||||
text already written by user.
|
||||
"""
|
||||
super(ReplApplication, self).complete(text, state)
|
||||
|
|
|
|||
|
|
@ -28,42 +28,103 @@ from weboob.tools.log import getLogger
|
|||
from weboob.tools.value import ValuesDict
|
||||
|
||||
|
||||
__all__ = ['BaseBackend', 'ObjectNotAvailable']
|
||||
__all__ = ['ObjectNotAvailable', 'BackendStorage', 'BackendConfig', 'BaseBackend']
|
||||
|
||||
|
||||
class ObjectNotAvailable(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when an object is not available.
|
||||
"""
|
||||
|
||||
|
||||
class BackendStorage(object):
|
||||
"""
|
||||
This is an abstract layer to store data in storages (:mod:`weboob.tools.storage`)
|
||||
easily.
|
||||
|
||||
It is instancied automatically in constructor of :class:`BaseBackend`, in the
|
||||
:attr:`BaseBackend.storage` attribute.
|
||||
|
||||
:param name: name of backend
|
||||
:param storage: storage object
|
||||
:type storage: :class:`weboob.tools.storage.IStorage`
|
||||
"""
|
||||
def __init__(self, name, storage):
|
||||
self.name = name
|
||||
self.storage = storage
|
||||
|
||||
def set(self, *args):
|
||||
"""
|
||||
Set value in the storage.
|
||||
|
||||
Example:
|
||||
|
||||
>>> backend.storage.set('config', 'nb_of_threads', 10)
|
||||
>>>
|
||||
|
||||
:param args: the path where to store value
|
||||
"""
|
||||
if self.storage:
|
||||
return self.storage.set('backends', self.name, *args)
|
||||
|
||||
def delete(self, *args):
|
||||
"""
|
||||
Delete a value from the storage.
|
||||
|
||||
:param args: path to delete.
|
||||
"""
|
||||
if self.storage:
|
||||
return self.storage.delete('backends', self.name, *args)
|
||||
|
||||
def get(self, *args, **kwargs):
|
||||
"""
|
||||
Get a value or a dict of values in storage.
|
||||
|
||||
Example:
|
||||
|
||||
>>> backend.storage.get('config', 'nb_of_threads')
|
||||
10
|
||||
>>> backend.storage.get('config', 'unexistant', 'path', default='lol')
|
||||
'lol'
|
||||
>>> backend.storage.get('config')
|
||||
{'nb_of_threads': 10, 'other_things': 'blah'}
|
||||
|
||||
:param args: path to get
|
||||
:param default: if specified, default value when path is not found
|
||||
"""
|
||||
if self.storage:
|
||||
return self.storage.get('backends', self.name, *args, **kwargs)
|
||||
else:
|
||||
return kwargs.get('default', None)
|
||||
|
||||
def load(self, default):
|
||||
"""
|
||||
Load storage.
|
||||
|
||||
:param default: this is the default tree if storage is empty
|
||||
:type default: :class:`dict`
|
||||
"""
|
||||
if self.storage:
|
||||
return self.storage.load('backends', self.name, default)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save storage.
|
||||
"""
|
||||
if self.storage:
|
||||
return self.storage.save('backends', self.name)
|
||||
|
||||
|
||||
class BackendConfig(ValuesDict):
|
||||
"""
|
||||
Configuration of a backend.
|
||||
|
||||
This class is firstly instanced as a :class:`weboob.tools.value.ValuesDict`,
|
||||
containing some :class:`weboob.tools.value.Value` (and derivated) objects.
|
||||
|
||||
Then, using the :func:`load` method will load configuration from file and
|
||||
create a copy of the :class:`BackendConfig` object with the loaded values.
|
||||
"""
|
||||
modname = None
|
||||
instname = None
|
||||
weboob = None
|
||||
|
|
@ -72,12 +133,17 @@ class BackendConfig(ValuesDict):
|
|||
"""
|
||||
Load configuration from dict to create an instance.
|
||||
|
||||
@param weboob [Weboob] weboob object
|
||||
@param modname [str] name of module
|
||||
@param instname [str] name of instance of this backend
|
||||
@param params [dict] parameters to load
|
||||
@param nofail [bool] if true, this call can't fail.
|
||||
@return [BackendConfig]
|
||||
:param weboob: weboob object
|
||||
:type weboob: :class:`weboob.core.ouiboube.Weboob`
|
||||
:param modname: name of the module
|
||||
:type modname: :class:`str`
|
||||
:param instname: name of this backend
|
||||
:type instname: :class:`str`
|
||||
:param params: parameters to load
|
||||
:type params: :class:`dict`
|
||||
:param nofail: if true, this call can't fail
|
||||
:type nofail: :class:`bool`
|
||||
:rtype: :class:`BackendConfig`
|
||||
"""
|
||||
cfg = BackendConfig()
|
||||
cfg.modname = modname
|
||||
|
|
@ -103,12 +169,25 @@ class BackendConfig(ValuesDict):
|
|||
return cfg
|
||||
|
||||
def dump(self):
|
||||
"""
|
||||
Dump config in a dictionary.
|
||||
|
||||
:rtype: :class:`dict`
|
||||
"""
|
||||
settings = {}
|
||||
for name, value in self.iteritems():
|
||||
settings[name] = value.dump()
|
||||
return settings
|
||||
|
||||
def save(self, edit=True, params=None):
|
||||
"""
|
||||
Save backend config.
|
||||
|
||||
:param edit: if true, it changes config of an existing backend
|
||||
:type edit: :class:`bool`
|
||||
:param params: if specified, params to merge with the ones of the current object
|
||||
:type params: :class:`dict`
|
||||
"""
|
||||
assert self.modname is not None
|
||||
assert self.instname is not None
|
||||
assert self.weboob is not None
|
||||
|
|
@ -121,6 +200,22 @@ class BackendConfig(ValuesDict):
|
|||
|
||||
|
||||
class BaseBackend(object):
|
||||
"""
|
||||
Base class for backends.
|
||||
|
||||
You may derivate it, and also all capabilities you want to implement.
|
||||
|
||||
:param weboob: weboob instance
|
||||
:type weboob: :class:`weboob.core.ouiboube.Weboob`
|
||||
:param name: name of backend
|
||||
:type name: :class:`str`
|
||||
:param config: configuration of backend
|
||||
:type config: :class:`dict`
|
||||
:param storage: storage object
|
||||
:type storage: :class:`weboob.tools.storage.IStorage`
|
||||
:param logger: logger
|
||||
:type logger: :class:`logging.Logger`
|
||||
"""
|
||||
# Backend name.
|
||||
NAME = None
|
||||
# Name of the maintainer of this backend.
|
||||
|
|
@ -152,7 +247,9 @@ class BaseBackend(object):
|
|||
OBJECTS = {}
|
||||
|
||||
class ConfigError(Exception):
|
||||
pass
|
||||
"""
|
||||
Raised when the config can't be loaded.
|
||||
"""
|
||||
|
||||
def __enter__(self):
|
||||
self.lock.acquire()
|
||||
|
|
@ -184,16 +281,6 @@ class BaseBackend(object):
|
|||
"""
|
||||
pass
|
||||
|
||||
class classprop(object):
|
||||
def __init__(self, fget):
|
||||
self.fget = fget
|
||||
|
||||
def __get__(self, inst, objtype=None):
|
||||
if inst:
|
||||
return self.fget(inst)
|
||||
else:
|
||||
return self.fget(objtype)
|
||||
|
||||
_browser = None
|
||||
|
||||
@property
|
||||
|
|
@ -202,7 +289,7 @@ class BaseBackend(object):
|
|||
Attribute 'browser'. The browser is created at the first call
|
||||
of this attribute, to avoid useless pages access.
|
||||
|
||||
Note that the 'create_default_browser' method is called to create it.
|
||||
Note that the :func:`create_default_browser` method is called to create it.
|
||||
"""
|
||||
if self._browser is None:
|
||||
self._browser = self.create_default_browser()
|
||||
|
|
@ -238,6 +325,11 @@ class BaseBackend(object):
|
|||
|
||||
@classmethod
|
||||
def iter_caps(klass):
|
||||
"""
|
||||
Iter capabilities implemented by this backend.
|
||||
|
||||
:rtype: iter[:class:`weboob.capabilities.base.IBaseCap`]
|
||||
"""
|
||||
def iter_caps(cls):
|
||||
for base in cls.__bases__:
|
||||
if issubclass(base, IBaseCap) and base != IBaseCap:
|
||||
|
|
@ -247,6 +339,9 @@ class BaseBackend(object):
|
|||
return iter_caps(klass)
|
||||
|
||||
def has_caps(self, *caps):
|
||||
"""
|
||||
Check if this backend implements at least one of these capabilities.
|
||||
"""
|
||||
for c in caps:
|
||||
if (isinstance(c, basestring) and c in [cap.__name__ for cap in self.iter_caps()]) or \
|
||||
isinstance(self, c):
|
||||
|
|
@ -255,7 +350,10 @@ class BaseBackend(object):
|
|||
|
||||
def fillobj(self, obj, fields=None):
|
||||
"""
|
||||
@param fields which fields to fill; if None, all fields are filled (list)
|
||||
Fill an object with the wanted fields.
|
||||
|
||||
:param fields: what fields to fill; if None, all fields are filled
|
||||
:type fields: :class:`list`
|
||||
"""
|
||||
def not_loaded(v):
|
||||
return (v is NotLoaded or isinstance(v, CapBaseObject) and not v.__iscomplete__())
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ else:
|
|||
|
||||
|
||||
__all__ = ['BrowserIncorrectPassword', 'BrowserBanned', 'BrowserUnavailable', 'BrowserRetry',
|
||||
'BrowserHTTPNotFound', 'BrowserHTTPError', 'BasePage', 'BaseBrowser', 'StandardBrowser']
|
||||
'BrowserHTTPNotFound', 'BrowserHTTPError', 'BrokenPageError', 'BasePage',
|
||||
'StandardBrowser', 'BaseBrowser']
|
||||
|
||||
|
||||
# Exceptions
|
||||
|
|
@ -135,6 +136,21 @@ def check_location(func):
|
|||
return inner
|
||||
|
||||
class StandardBrowser(mechanize.Browser):
|
||||
"""
|
||||
Standard Browser.
|
||||
|
||||
:param firefox_cookies: path to cookies sqlite file
|
||||
:type firefox_cookies: str
|
||||
:param parser: parser to use on HTML files
|
||||
:type parser: :class:`weboob.tools.parsers.iparser.IParser`
|
||||
:param history: history manager; default value is an object which
|
||||
does not keep history
|
||||
:type history: object
|
||||
:param proxy: proxy URL to use
|
||||
:type proxy: str
|
||||
:param factory: mechanize factory. None to use Mechanize's default
|
||||
:type factory: object
|
||||
"""
|
||||
|
||||
# ------ Class attributes --------------------------------------
|
||||
|
||||
|
|
@ -161,16 +177,6 @@ class StandardBrowser(mechanize.Browser):
|
|||
default_features.remove('_refresh')
|
||||
|
||||
def __init__(self, firefox_cookies=None, parser=None, history=NoHistory(), proxy=None, logger=None, factory=None, responses_dirname=None):
|
||||
"""
|
||||
Constructor of Browser.
|
||||
|
||||
@param filefox_cookies [str] Path to cookies' sqlite file.
|
||||
@param parser [IParser] parser to use on HTML files.
|
||||
@param history [object] History manager. Default value is an object
|
||||
which does not keep history.
|
||||
@param proxy [str] proxy URL to use.
|
||||
@param factory [object] Mechanize factory. None to use Mechanize's default.
|
||||
"""
|
||||
mechanize.Browser.__init__(self, history=history, factory=factory)
|
||||
self.logger = getLogger('browser', logger)
|
||||
|
||||
|
|
@ -309,15 +315,16 @@ class StandardBrowser(mechanize.Browser):
|
|||
def buildurl(base, *args, **kwargs):
|
||||
"""
|
||||
Build an URL and escape arguments.
|
||||
You can give a serie of tuples in *args (and the order is keept), or
|
||||
a dict in **kwargs (but the order is lost).
|
||||
|
||||
You can give a serie of tuples in args (and the order is keept), or
|
||||
a dict in kwargs (but the order is lost).
|
||||
|
||||
Example:
|
||||
|
||||
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
|
||||
'/blah.php?a=%26&b=%3D'
|
||||
>>> buildurl('/blah.php', a='&', 'b'='=')
|
||||
'/blah.php?b=%3D&a=%26'
|
||||
|
||||
"""
|
||||
|
||||
if not args:
|
||||
|
|
@ -336,11 +343,16 @@ class StandardBrowser(mechanize.Browser):
|
|||
"""
|
||||
Set a value to a form field.
|
||||
|
||||
@param args [dict] arguments where to look for value.
|
||||
@param label [str] label in args.
|
||||
@param field [str] field name. If None, use label instead.
|
||||
@param value [str] value to give on field.
|
||||
@param is_list [bool] the field is a list.
|
||||
:param args: arguments where to look for value
|
||||
:type args: dict
|
||||
:param label: label in args
|
||||
:type label: str
|
||||
:param field: field name. If None, use label instead
|
||||
:type field: str
|
||||
:param value: value to give on field
|
||||
:type value: str
|
||||
:param is_list: the field is a list
|
||||
:type is_list: bool
|
||||
"""
|
||||
try:
|
||||
if not field:
|
||||
|
|
@ -366,6 +378,29 @@ class StandardBrowser(mechanize.Browser):
|
|||
class BaseBrowser(StandardBrowser):
|
||||
"""
|
||||
Base browser class to navigate on a website.
|
||||
|
||||
:param username: username on website
|
||||
:type username: str
|
||||
:param password: password on website. If it is None, Browser will
|
||||
not try to login
|
||||
:type password: str
|
||||
:param firefox_cookies: path to cookies sqlite file
|
||||
:type firefox_cookies: str
|
||||
:param parser: parser to use on HTML files
|
||||
:type parser: :class:`weboob.tools.parsers.iparser.IParser`
|
||||
:param history: history manager; default value is an object which
|
||||
does not keep history
|
||||
:type history: object
|
||||
:param proxy: proxy URL to use
|
||||
:type proxy: str
|
||||
:param logger: logger to use for logging
|
||||
:type logger: :class:`logging.Logger`
|
||||
:param factory: mechanize factory. None to use Mechanize's default
|
||||
:type factory: object
|
||||
:param get_home: try to get the homepage.
|
||||
:type get_homme: bool
|
||||
:param responses_dirname: directory to store responses
|
||||
:type responses_dirname: str
|
||||
"""
|
||||
|
||||
# ------ Class attributes --------------------------------------
|
||||
|
|
@ -406,20 +441,6 @@ class BaseBrowser(StandardBrowser):
|
|||
def __init__(self, username=None, password=None, firefox_cookies=None,
|
||||
parser=None, history=NoHistory(), proxy=None, logger=None,
|
||||
factory=None, get_home=True, responses_dirname=None):
|
||||
"""
|
||||
Constructor of Browser.
|
||||
|
||||
@param username [str] username on website.
|
||||
@param password [str] password on website. If it is None, Browser will
|
||||
not try to login.
|
||||
@param filefox_cookies [str] Path to cookies' sqlite file.
|
||||
@param parser [IParser] parser to use on HTML files.
|
||||
@param hisory [object] History manager. Default value is an object
|
||||
which does not keep history.
|
||||
@param proxy [str] proxy URL to use.
|
||||
@param factory [object] Mechanize factory. None to use Mechanize's default.
|
||||
@param get_home [bool] Try to get the homepage.
|
||||
"""
|
||||
StandardBrowser.__init__(self, firefox_cookies, parser, history, proxy, logger, factory, responses_dirname)
|
||||
self.page = None
|
||||
self.last_update = 0.0
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ __all__ = ['FrenchTransaction']
|
|||
|
||||
|
||||
class FrenchTransaction(Transaction):
|
||||
"""
|
||||
Transaction with some helpers for french bank websites.
|
||||
"""
|
||||
PATTERNS = []
|
||||
|
||||
def clean_amount(self, text):
|
||||
|
|
@ -61,7 +64,7 @@ class FrenchTransaction(Transaction):
|
|||
|
||||
When calling this method, you should have defined patterns (in the
|
||||
PATTERN class attribute) with a list containing tuples of regexp
|
||||
and the associated type, for example:
|
||||
and the associated type, for example::
|
||||
|
||||
PATTERNS = [(re.compile('^VIR(EMENT)? (?P<text>.*)'), FrenchTransaction.TYPE_TRANSFER),
|
||||
(re.compile('^PRLV (?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
|
||||
|
|
@ -70,8 +73,9 @@ class FrenchTransaction(Transaction):
|
|||
]
|
||||
|
||||
In regexps, you can define this patterns:
|
||||
- text: part of label to store in simplified label
|
||||
- yy, mm, dd, HH, MM: date and time parts
|
||||
|
||||
* text: part of label to store in simplified label
|
||||
* yy, mm, dd, HH, MM: date and time parts
|
||||
"""
|
||||
if not isinstance(date, (datetime.date, datetime.datetime)):
|
||||
if date.isdigit() and len(date) == 8:
|
||||
|
|
|
|||
|
|
@ -27,7 +27,9 @@ from weboob.tools.newsfeed import Newsfeed
|
|||
|
||||
|
||||
class GenericNewspaperBackend(BaseBackend, ICapMessages):
|
||||
"GenericNewspaperBackend class"
|
||||
"""
|
||||
GenericNewspaperBackend class
|
||||
"""
|
||||
MAINTAINER = 'Julien Hebert'
|
||||
EMAIL = 'juke@free.fr'
|
||||
VERSION = '0.c'
|
||||
|
|
|
|||
|
|
@ -17,13 +17,23 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from weboob.capabilities.base import CapBaseObject, NotLoaded
|
||||
from weboob.capabilities.base import CapBaseObject, NotLoaded, StringField, BytesField
|
||||
|
||||
|
||||
__all__ = ['Thumbnail']
|
||||
|
||||
|
||||
class Thumbnail(CapBaseObject):
|
||||
"""
|
||||
Thumbnail of an image.
|
||||
"""
|
||||
|
||||
url = StringField('URL to photo thumbnail')
|
||||
data = BytesField('Data')
|
||||
|
||||
def __init__(self, url):
|
||||
CapBaseObject.__init__(self, url)
|
||||
self.add_field('url', basestring, url.replace(' ', '%20'))
|
||||
self.add_field('data', str)
|
||||
self.url = url.replace(' ', '%20')
|
||||
|
||||
def __str__(self):
|
||||
return self.url
|
||||
|
|
|
|||
|
|
@ -60,14 +60,18 @@ class LxmlHtmlParser(IParser):
|
|||
"""
|
||||
Select one or many elements from an element, using lxml cssselect by default.
|
||||
|
||||
Raises BrokenPageError if not found.
|
||||
Raises :class:`weboob.tools.browser.browser.BrokenPageError` if not found.
|
||||
|
||||
@param element [obj] element on which to apply selector
|
||||
@param selector [str] CSS or XPath expression
|
||||
@param method [str] (cssselect|xpath)
|
||||
@param nb [int] number of elements expected to be found.
|
||||
Use None for undefined number, and 'many' for 1 to infinite.
|
||||
@return one or many Element
|
||||
:param element: element on which to apply selector
|
||||
:type element: object
|
||||
:param selector: CSS or XPath expression
|
||||
:type selector: str
|
||||
:param method: (cssselect|xpath)
|
||||
:type method: str
|
||||
:param nb: number of elements expected to be found. Use None for
|
||||
undefined number, and 'many' for 1 to infinite
|
||||
:type nb: :class:`int` or :class:`str`
|
||||
:rtype: Element
|
||||
"""
|
||||
if method == 'cssselect':
|
||||
results = element.cssselect(selector)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue