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')
|
os.chdir('api')
|
||||||
for root, dirs, files in os.walk('../../../weboob/'):
|
for root, dirs, files in os.walk('../../../weboob/'):
|
||||||
root = root.split('/', 4)[-1]
|
root = root.split('/', 4)[-1]
|
||||||
if root.startswith('applications') or \
|
if root.startswith('applications'):
|
||||||
root.startswith('backends'):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if root.strip():
|
if root.strip():
|
||||||
|
|
@ -26,7 +25,7 @@ def genapi():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
f, ext = f.rsplit('.', 1)
|
f, ext = f.rsplit('.', 1)
|
||||||
if ext == 'pyc' or f == '__init__':
|
if ext != 'py' or f == '__init__':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
subs.add(f)
|
subs.add(f)
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,12 @@ Weboob
|
||||||
|
|
||||||
This is the developer documentation.
|
This is the developer documentation.
|
||||||
|
|
||||||
.. warning::
|
|
||||||
This documentation is being written.
|
|
||||||
|
|
||||||
Contents:
|
Contents:
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
overview
|
overview
|
||||||
install
|
|
||||||
backends
|
|
||||||
applications
|
|
||||||
guides/index
|
guides/index
|
||||||
api/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:
|
Weboob (Web Out Of Browsers) provides:
|
||||||
|
|
||||||
* :doc:`applications` to interact with websites
|
* :doc:`applications <api/tools/application/index>` to interact with websites
|
||||||
* :doc:`backends`, each one handles a specific website
|
* :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
|
* 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
|
* :doc:`tools <api/tools/index>` to help develop backends and applications
|
||||||
|
|
||||||
Weboob is written in Python and is distributed under the AGPLv3+ license.
|
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
|
Architecture
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,23 +18,32 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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):
|
class AccountRegisterError(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when there is an error during registration.
|
||||||
|
"""
|
||||||
|
|
||||||
class Account(CapBaseObject):
|
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):
|
def __init__(self, id=None):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('login', basestring)
|
|
||||||
self.add_field('password', basestring)
|
|
||||||
self.add_field('properties', dict)
|
|
||||||
|
|
||||||
class StatusField(object):
|
class StatusField(object):
|
||||||
|
"""
|
||||||
|
Field of an account status.
|
||||||
|
"""
|
||||||
FIELD_TEXT = 0x001 # the value is a long text
|
FIELD_TEXT = 0x001 # the value is a long text
|
||||||
FIELD_HTML = 0x002 # the value is HTML formated
|
FIELD_HTML = 0x002 # the value is HTML formated
|
||||||
|
|
||||||
|
|
@ -46,8 +55,14 @@ class StatusField(object):
|
||||||
|
|
||||||
|
|
||||||
class ICapAccount(IBaseCap):
|
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
|
ACCOUNT_REGISTER_PROPERTIES = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -58,7 +73,9 @@ class ICapAccount(IBaseCap):
|
||||||
This is a static method, it would be called even if the backend is
|
This is a static method, it would be called even if the backend is
|
||||||
instancied.
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -84,6 +101,6 @@ class ICapAccount(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get status of the current account.
|
Get status of the current account.
|
||||||
|
|
||||||
@return a list of fields
|
:returns: a list of fields
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,30 +18,45 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
from .collection import ICapCollection
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Account', 'AccountNotFound', 'TransferError', 'ICapBank', 'Transaction']
|
__all__ = ['AccountNotFound', 'TransferError', 'Recipient', 'Account', 'Transaction', 'Transfer', 'ICapBank']
|
||||||
|
|
||||||
|
|
||||||
class AccountNotFound(Exception):
|
class AccountNotFound(Exception):
|
||||||
def __init__(self, msg=None):
|
"""
|
||||||
if msg is None:
|
Raised when an account is not found.
|
||||||
msg = 'Account not found'
|
"""
|
||||||
|
|
||||||
|
def __init__(self, msg='Account not found'):
|
||||||
Exception.__init__(self, msg)
|
Exception.__init__(self, msg)
|
||||||
|
|
||||||
class TransferError(Exception):
|
class TransferError(Exception):
|
||||||
pass
|
"""
|
||||||
|
A transfer has failed.
|
||||||
|
"""
|
||||||
|
|
||||||
class Recipient(CapBaseObject):
|
class Recipient(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Recipient of a transfer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
label = StringField('Name')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
CapBaseObject.__init__(self, 0)
|
CapBaseObject.__init__(self, 0)
|
||||||
self.add_field('label', basestring)
|
|
||||||
|
|
||||||
class Account(Recipient):
|
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_UNKNOWN = 0
|
||||||
TYPE_CHECKING = 1 # Transaction, everyday transactions
|
TYPE_CHECKING = 1 # Transaction, everyday transactions
|
||||||
TYPE_SAVINGS = 2 # Savings/Deposit, can be used for everyday banking
|
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_MARKET = 5 # Stock market or other variable investments
|
||||||
TYPE_JOINT = 6 # Joint account
|
TYPE_JOINT = 6 # Joint account
|
||||||
|
|
||||||
def __init__(self):
|
type = IntField('Type of account', default=TYPE_UNKNOWN)
|
||||||
Recipient.__init__(self)
|
balance = FloatField('Balance on this bank account')
|
||||||
self.add_field('type', int, self.TYPE_UNKNOWN)
|
coming = FloatField('Coming balance')
|
||||||
self.add_field('balance', float)
|
|
||||||
self.add_field('coming', float)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return u"<Account id=%r label=%r>" % (self.id, self.label)
|
return u"<Account id=%r label=%r>" % (self.id, self.label)
|
||||||
|
|
||||||
|
|
||||||
class Transaction(CapBaseObject):
|
class Transaction(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Bank transaction.
|
||||||
|
"""
|
||||||
TYPE_UNKNOWN = 0
|
TYPE_UNKNOWN = 0
|
||||||
TYPE_TRANSFER = 1
|
TYPE_TRANSFER = 1
|
||||||
TYPE_ORDER = 2
|
TYPE_ORDER = 2
|
||||||
|
|
@ -72,47 +88,77 @@ class Transaction(CapBaseObject):
|
||||||
TYPE_LOAN_PAYMENT = 8
|
TYPE_LOAN_PAYMENT = 8
|
||||||
TYPE_BANK = 9
|
TYPE_BANK = 9
|
||||||
|
|
||||||
def __init__(self, id):
|
date = DateField('Debit date')
|
||||||
CapBaseObject.__init__(self, id)
|
rdate = DateField('Real date, when the payment has been made')
|
||||||
self.add_field('date', (basestring, datetime, date)) # debit date
|
type = IntField('Type of transaction, use TYPE_* constants', default=TYPE_UNKNOWN)
|
||||||
self.add_field('rdate', (datetime, date)) # real date, when the payment has been made
|
raw = StringField('Raw label of the transaction')
|
||||||
self.add_field('type', int, self.TYPE_UNKNOWN)
|
category = StringField('Category of transaction')
|
||||||
self.add_field('raw', unicode)
|
label = StringField('Pretty label')
|
||||||
self.add_field('category', unicode)
|
amount = FloatField('Amount of transaction')
|
||||||
self.add_field('label', unicode)
|
|
||||||
self.add_field('amount', float)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Transaction date='%s' label='%s' amount=%s>" % (self.date,
|
return "<Transaction date='%s' label='%s' amount=%s>" % (self.date,
|
||||||
self.label, self.amount)
|
self.label, self.amount)
|
||||||
|
|
||||||
class Transfer(CapBaseObject):
|
class Transfer(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Transfer from an account to a recipient.
|
||||||
self.add_field('amount', float)
|
"""
|
||||||
self.add_field('date', (basestring, datetime, date))
|
|
||||||
self.add_field('origin', (int, long, basestring))
|
amount = FloatField('Amount to transfer')
|
||||||
self.add_field('recipient', (int, long, basestring))
|
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):
|
class ICapBank(ICapCollection):
|
||||||
|
"""
|
||||||
|
Capability of bank websites to see accounts and transactions.
|
||||||
|
"""
|
||||||
def iter_resources(self, objs, split_path):
|
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:
|
if Account in objs:
|
||||||
self._restrict_level(split_path)
|
self._restrict_level(split_path)
|
||||||
|
|
||||||
return self.iter_accounts()
|
return self.iter_accounts()
|
||||||
|
|
||||||
def iter_accounts(self):
|
def iter_accounts(self):
|
||||||
|
"""
|
||||||
|
Iter accounts.
|
||||||
|
|
||||||
|
:rtype: iter[:class:`Account`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def iter_history(self, account):
|
def iter_history(self, account):
|
||||||
"""
|
"""
|
||||||
Iter history of transactions on a specific account.
|
Iter history of transactions on a specific account.
|
||||||
|
|
||||||
@param account [Account]
|
:param account: account to get history
|
||||||
@return [iter(Transaction)]
|
:type account: :class:`Account`
|
||||||
|
:rtype: iter[:class:`Transaction`]
|
||||||
|
:raises: :class:`AccountNotFound`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -120,8 +166,10 @@ class ICapBank(ICapCollection):
|
||||||
"""
|
"""
|
||||||
Iter coming transactions on a specific account.
|
Iter coming transactions on a specific account.
|
||||||
|
|
||||||
@param account [Account]
|
:param account: account to get coming transactions
|
||||||
@return [iter(Transaction)]
|
:type account: :class:`Account`
|
||||||
|
:rtype: iter[:class:`Transaction`]
|
||||||
|
:raises: :class:`AccountNotFound`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -129,8 +177,10 @@ class ICapBank(ICapCollection):
|
||||||
"""
|
"""
|
||||||
Iter recipients availables for a transfer from a specific account.
|
Iter recipients availables for a transfer from a specific account.
|
||||||
|
|
||||||
@param account [Account] account which initiate the transfer
|
:param account: account which initiate the transfer
|
||||||
@return [iter(Recipient)]
|
:type account: :class:`Account`
|
||||||
|
:rtype: iter[:class:`Recipient`]
|
||||||
|
:raises: :class:`AccountNotFound`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -138,10 +188,15 @@ class ICapBank(ICapCollection):
|
||||||
"""
|
"""
|
||||||
Make a transfer from an account to a recipient.
|
Make a transfer from an account to a recipient.
|
||||||
|
|
||||||
@param account [Account] account to take money
|
:param account: account to take money
|
||||||
@param recipient [Recipient] account to send money
|
:type account: :class:`Account`
|
||||||
@param amount [float] amount
|
:param recipient: account to send money
|
||||||
@param reason [str] reason of transfer
|
:type recipient: :class:`Recipient`
|
||||||
@return [Transfer] a Transfer object
|
: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()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,28 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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',
|
__all__ = ['FieldNotFound', 'NotAvailable', 'NotLoaded', 'IBaseCap',
|
||||||
'CapBaseObject']
|
'Field', 'IntField', 'FloatField', 'StringField', 'BytesField',
|
||||||
|
'DateField', 'DeltaField', 'CapBaseObject']
|
||||||
|
|
||||||
|
|
||||||
class FieldNotFound(Exception):
|
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):
|
def __init__(self, obj, field):
|
||||||
Exception.__init__(self,
|
Exception.__init__(self,
|
||||||
u'Field "%s" not found for object %s' % (field, obj))
|
u'Field "%s" not found for object %s' % (field, obj))
|
||||||
|
|
@ -43,6 +57,9 @@ class NotAvailableMeta(type):
|
||||||
|
|
||||||
|
|
||||||
class NotAvailable(object):
|
class NotAvailable(object):
|
||||||
|
"""
|
||||||
|
Constant to use on non available fields.
|
||||||
|
"""
|
||||||
__metaclass__ = NotAvailableMeta
|
__metaclass__ = NotAvailableMeta
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,41 +75,186 @@ class NotLoadedMeta(type):
|
||||||
|
|
||||||
|
|
||||||
class NotLoaded(object):
|
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
|
__metaclass__ = NotLoadedMeta
|
||||||
|
|
||||||
|
|
||||||
class IBaseCap(object):
|
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):
|
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):
|
def __init__(self, id, backend=None):
|
||||||
self.id = id
|
self.id = to_unicode(id)
|
||||||
self.backend = backend
|
self.backend = backend
|
||||||
|
self._fields = deepcopy(self._fields)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fullid(self):
|
def fullid(self):
|
||||||
|
"""
|
||||||
|
Full ID of the object, in form '**ID@backend**'.
|
||||||
|
"""
|
||||||
return '%s@%s' % (self.id, self.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):
|
def __iscomplete__(self):
|
||||||
"""
|
"""
|
||||||
Return True if the object is completed.
|
Return True if the object is completed.
|
||||||
|
|
@ -111,6 +273,9 @@ class CapBaseObject(object):
|
||||||
def set_empty_fields(self, value, excepts=()):
|
def set_empty_fields(self, value, excepts=()):
|
||||||
"""
|
"""
|
||||||
Set the same value on all empty fields.
|
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():
|
for key, old_value in self.iter_fields():
|
||||||
if old_value in (None, NotLoaded, NotAvailable) and \
|
if old_value in (None, NotLoaded, NotAvailable) and \
|
||||||
|
|
@ -119,22 +284,16 @@ class CapBaseObject(object):
|
||||||
|
|
||||||
def iter_fields(self):
|
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.
|
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
|
yield 'id', self.id
|
||||||
for key, value in iter_fields(self):
|
for name, field in self._fields.iteritems():
|
||||||
if key not in ('id', 'backend', 'FIELDS'):
|
yield name, field.value
|
||||||
yield key, value
|
|
||||||
else:
|
|
||||||
yield 'id', self.id
|
|
||||||
for attrstr in self.FIELDS:
|
|
||||||
yield attrstr, getattr(self, attrstr)
|
|
||||||
|
|
||||||
def __eq__(self, obj):
|
def __eq__(self, obj):
|
||||||
if isinstance(obj, CapBaseObject):
|
if isinstance(obj, CapBaseObject):
|
||||||
|
|
@ -142,29 +301,32 @@ class CapBaseObject(object):
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class _AttribValue(object):
|
|
||||||
def __init__(self, type, value):
|
|
||||||
self.type = type
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if self._attribs is not None and name in self._attribs:
|
if self._fields is not None and name in self._fields:
|
||||||
return self._attribs[name].value
|
return self._fields[name].value
|
||||||
else:
|
else:
|
||||||
raise AttributeError, "'%s' object has no attribute '%s'" % (
|
raise AttributeError, "'%s' object has no attribute '%s'" % (
|
||||||
self.__class__.__name__, name)
|
self.__class__.__name__, name)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
try:
|
try:
|
||||||
attr = (self._attribs or {})[name]
|
attr = (self._fields or {})[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
object.__setattr__(self, name, value)
|
object.__setattr__(self, name, value)
|
||||||
else:
|
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 NotLoaded and \
|
||||||
value is not NotAvailable and \
|
value is not NotAvailable and \
|
||||||
value is not None:
|
value is not None:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
'Value for "%s" needs to be of type %r, not %r' % (
|
'Value for "%s" needs to be of type %r, not %r' % (
|
||||||
name, attr.type, type(value)))
|
name, attr.types, type(value)))
|
||||||
attr.value = value
|
attr.value = value
|
||||||
|
|
|
||||||
|
|
@ -17,74 +17,133 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from datetime import datetime, date
|
from .base import CapBaseObject, StringField, DateField, FloatField
|
||||||
from .base import CapBaseObject
|
|
||||||
from .collection import ICapCollection
|
from .collection import ICapCollection
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['Subscription', 'SubscriptionNotFound', 'ICapBill', 'Detail']
|
__all__ = ['SubscriptionNotFound', 'BillNotFound', 'Detail', 'Bill', 'Subscription', 'ICapBill']
|
||||||
|
|
||||||
|
|
||||||
class SubscriptionNotFound(Exception):
|
class SubscriptionNotFound(Exception):
|
||||||
def __init__(self, msg=None):
|
"""
|
||||||
if msg is None:
|
Raised when a subscription is not found.
|
||||||
msg = 'Subscription not found'
|
"""
|
||||||
|
def __init__(self, msg='Subscription not found'):
|
||||||
Exception.__init__(self, msg)
|
Exception.__init__(self, msg)
|
||||||
|
|
||||||
|
|
||||||
class BillNotFound(Exception):
|
class BillNotFound(Exception):
|
||||||
def __init__(self, msg=None):
|
"""
|
||||||
if msg is None:
|
Raised when a bill is not found.
|
||||||
msg = 'Bill not found'
|
"""
|
||||||
|
def __init__(self, msg='Bill not found'):
|
||||||
Exception.__init__(self, msg)
|
Exception.__init__(self, msg)
|
||||||
|
|
||||||
|
|
||||||
class Detail(CapBaseObject):
|
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):
|
def __init__(self):
|
||||||
CapBaseObject.__init__(self, 0)
|
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):
|
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):
|
def __init__(self):
|
||||||
CapBaseObject.__init__(self, 0)
|
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):
|
class Subscription(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Subscription to a service.
|
||||||
self.add_field('label', basestring)
|
"""
|
||||||
self.add_field('subscriber', basestring)
|
label = StringField('label of subscription')
|
||||||
|
subscriber = StringField('whe has subscribed')
|
||||||
|
|
||||||
class ICapBill(ICapCollection):
|
class ICapBill(ICapCollection):
|
||||||
def iter_resources(self, objs, split_path):
|
def iter_resources(self, objs, split_path):
|
||||||
|
"""
|
||||||
|
Iter resources. Will return :func:`iter_subscriptions`.
|
||||||
|
"""
|
||||||
if Subscription in objs:
|
if Subscription in objs:
|
||||||
self._restrict_level(split_path)
|
self._restrict_level(split_path)
|
||||||
|
|
||||||
return self.iter_subscription()
|
return self.iter_subscription()
|
||||||
|
|
||||||
def iter_subscription(self):
|
def iter_subscription(self):
|
||||||
|
"""
|
||||||
|
Iter subscriptions.
|
||||||
|
|
||||||
|
:rtype: iter[:class:`Subscription`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_subscription(self, _id):
|
def get_subscription(self, _id):
|
||||||
|
"""
|
||||||
|
Get a subscription.
|
||||||
|
|
||||||
|
:param _id: ID of subscription
|
||||||
|
:rtype: :class:`Subscription`
|
||||||
|
:raises: :class:`SubscriptionNotFound`
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def iter_history(self, subscription):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_bill(self, id):
|
def get_bill(self, id):
|
||||||
|
"""
|
||||||
|
Get a bill.
|
||||||
|
|
||||||
|
:param id: ID of bill
|
||||||
|
:rtype: :class:`Bill`
|
||||||
|
:raises: :class:`BillNotFound`
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def download_bill(self, id):
|
def download_bill(self, id):
|
||||||
|
"""
|
||||||
|
Download a bill.
|
||||||
|
|
||||||
|
:param id: ID of bill
|
||||||
|
:rtype: str
|
||||||
|
:raises: :class:`BillNotFound`
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def iter_bills(self, subscription):
|
def iter_bills(self, subscription):
|
||||||
|
"""
|
||||||
|
Iter bills.
|
||||||
|
|
||||||
|
:param subscription: subscription to get bills
|
||||||
|
:type subscription: :class:`Subscription`
|
||||||
|
:rtype: iter[:class:`Bill`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_details(self, subscription):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -17,30 +17,49 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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):
|
class IssueError(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when there is an error with an issue.
|
||||||
|
"""
|
||||||
|
|
||||||
class Project(CapBaseObject):
|
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):
|
def __init__(self, id, name):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', unicode, name)
|
self.name = name
|
||||||
self.add_field('members', list)
|
|
||||||
self.add_field('versions', list)
|
|
||||||
self.add_field('categories', list)
|
|
||||||
self.add_field('statuses', list)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Project %r>' % self.name
|
return '<Project %r>' % self.name
|
||||||
|
|
||||||
def find_user(self, id, 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:
|
for user in self.members:
|
||||||
if user.id == id:
|
if user.id == id:
|
||||||
return user
|
return user
|
||||||
|
|
@ -49,6 +68,17 @@ class Project(CapBaseObject):
|
||||||
return User(id, name)
|
return User(id, name)
|
||||||
|
|
||||||
def find_version(self, 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:
|
for version in self.versions:
|
||||||
if version.id == id:
|
if version.id == id:
|
||||||
return version
|
return version
|
||||||
|
|
@ -57,6 +87,13 @@ class Project(CapBaseObject):
|
||||||
return Version(id, name)
|
return Version(id, name)
|
||||||
|
|
||||||
def find_status(self, 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:
|
for status in self.statuses:
|
||||||
if status.name == name:
|
if status.name == name:
|
||||||
return status
|
return status
|
||||||
|
|
@ -65,98 +102,129 @@ class Project(CapBaseObject):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
class User(CapBaseObject):
|
class User(CapBaseObject):
|
||||||
|
"""
|
||||||
|
User.
|
||||||
|
"""
|
||||||
|
name = StringField('Name of user')
|
||||||
|
|
||||||
def __init__(self, id, name):
|
def __init__(self, id, name):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', unicode, name)
|
self.name = name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<User %r>' % self.name
|
return '<User %r>' % self.name
|
||||||
|
|
||||||
class Version(CapBaseObject):
|
class Version(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Version of a project.
|
||||||
|
"""
|
||||||
|
name = StringField('Name of version')
|
||||||
|
|
||||||
def __init__(self, id, name):
|
def __init__(self, id, name):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', unicode, name)
|
self.name = name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Version %r>' % self.name
|
return '<Version %r>' % self.name
|
||||||
|
|
||||||
class Status(CapBaseObject):
|
class Status(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Status of an issue.
|
||||||
|
|
||||||
|
**VALUE_** constants are the primary status
|
||||||
|
types.
|
||||||
|
"""
|
||||||
(VALUE_NEW,
|
(VALUE_NEW,
|
||||||
VALUE_PROGRESS,
|
VALUE_PROGRESS,
|
||||||
VALUE_RESOLVED,
|
VALUE_RESOLVED,
|
||||||
VALUE_REJECTED) = range(4)
|
VALUE_REJECTED) = range(4)
|
||||||
|
|
||||||
|
name = StringField('Name of status')
|
||||||
|
value = IntField('Value of status (constants VALUE_*)')
|
||||||
|
|
||||||
def __init__(self, id, name, value):
|
def __init__(self, id, name, value):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', unicode, name)
|
self.name = name
|
||||||
self.add_field('value', int, value)
|
self.value = value
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Status %r>' % self.name
|
return '<Status %r>' % self.name
|
||||||
|
|
||||||
class Attachment(CapBaseObject):
|
class Attachment(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Attachment of an issue.
|
||||||
self.add_field('filename', basestring)
|
"""
|
||||||
self.add_field('url', basestring)
|
filename = StringField('Filename')
|
||||||
|
url = StringField('Direct URL to attachment')
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Attachment %r>' % self.filename
|
return '<Attachment %r>' % self.filename
|
||||||
|
|
||||||
class Change(CapBaseObject):
|
class Change(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
A change of an update.
|
||||||
self.add_field('field', unicode)
|
"""
|
||||||
self.add_field('last', unicode)
|
field = StringField('What field has been changed')
|
||||||
self.add_field('new', unicode)
|
last = StringField('Last value of field')
|
||||||
|
new = StringField('New value of field')
|
||||||
|
|
||||||
class Update(CapBaseObject):
|
class Update(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Represents an update of an issue.
|
||||||
self.add_field('author', User)
|
"""
|
||||||
self.add_field('date', datetime)
|
author = Field('Author of update', User)
|
||||||
self.add_field('hours', timedelta)
|
date = DateField('Date of update')
|
||||||
self.add_field('message', unicode)
|
hours = DeltaField('Time activity')
|
||||||
self.add_field('attachments', (list,tuple)) # Attachment
|
message = StringField('Log message')
|
||||||
self.add_field('changes', (list,tuple)) # Change
|
attachments = Field('Files attached to update', list, tuple)
|
||||||
|
changes = Field('List of changes', list, tuple)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Update %r>' % self.id
|
return '<Update %r>' % self.id
|
||||||
|
|
||||||
class Issue(CapBaseObject):
|
class Issue(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Represents an issue.
|
||||||
self.add_field('project', Project)
|
"""
|
||||||
self.add_field('title', unicode)
|
project = Field('Project of this issue', Project)
|
||||||
self.add_field('body', unicode)
|
title = StringField('Title of issue')
|
||||||
self.add_field('creation', datetime)
|
body = StringField('Text of issue')
|
||||||
self.add_field('updated', datetime)
|
creation = DateField('Date when this issue has been created')
|
||||||
self.add_field('attachments', (list,tuple))
|
updated = DateField('Date when this issue has been updated for the last time')
|
||||||
self.add_field('history', (list,tuple))
|
attachments = Field('List of attached files', list, tuple)
|
||||||
self.add_field('author', User)
|
history = Field('History of updates', list, tuple)
|
||||||
self.add_field('assignee', User)
|
author = Field('Author of this issue', User)
|
||||||
self.add_field('category', unicode)
|
assignee = Field('User assigned to this issue', User)
|
||||||
self.add_field('version', Version)
|
category = StringField('Name of the category')
|
||||||
self.add_field('status', Status)
|
version = Field('Target version of this issue', Version)
|
||||||
|
status = Field('Status of this issue', Status)
|
||||||
|
|
||||||
class Query(CapBaseObject):
|
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):
|
def __init__(self):
|
||||||
CapBaseObject.__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):
|
class ICapBugTracker(IBaseCap):
|
||||||
|
"""
|
||||||
|
Bug trackers websites.
|
||||||
|
"""
|
||||||
def iter_issues(self, query):
|
def iter_issues(self, query):
|
||||||
"""
|
"""
|
||||||
Iter issues with optionnal patterns.
|
Iter issues with optionnal patterns.
|
||||||
|
|
||||||
@param query [Query]
|
:param query: query
|
||||||
@return [iter(Issue)] issues
|
:type query: :class:`Query`
|
||||||
|
:rtype: iter[:class:`Issue`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -164,7 +232,8 @@ class ICapBugTracker(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get an issue from its ID.
|
Get an issue from its ID.
|
||||||
|
|
||||||
@return Issue
|
:param id: ID of issue
|
||||||
|
:rtype: :class:`Issue`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -172,13 +241,19 @@ class ICapBugTracker(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Create an empty issue on the given project.
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def post_issue(self, issue):
|
def post_issue(self, issue):
|
||||||
"""
|
"""
|
||||||
Post an issue to create or update it.
|
Post an issue to create or update it.
|
||||||
|
|
||||||
|
:param issue: issue to create or update
|
||||||
|
:type issue: :class:`Issue`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -186,14 +261,19 @@ class ICapBugTracker(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Add an update to an issue.
|
Add an update to an issue.
|
||||||
|
|
||||||
@param issue [id,Issue] issue or id of issue
|
:param issue: issue or id of issue
|
||||||
@param update [Update] an Update object
|
:type issue: :class:`Issue`
|
||||||
|
:param update: an Update object
|
||||||
|
:type update: :class:`Update`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def remove_issue(self, issue):
|
def remove_issue(self, issue):
|
||||||
"""
|
"""
|
||||||
Remove an issue.
|
Remove an issue.
|
||||||
|
|
||||||
|
:param issue: issue
|
||||||
|
:type issue: :class:`Issue`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -201,7 +281,7 @@ class ICapBugTracker(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Iter projects.
|
Iter projects.
|
||||||
|
|
||||||
@return [iter(Project)] projects
|
:rtype: iter[:class:`Project`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -209,6 +289,6 @@ class ICapBugTracker(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get a project from its ID.
|
Get a project from its ID.
|
||||||
|
|
||||||
@return [Project]
|
:rtype: :class:`Project`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -20,31 +20,60 @@
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from .base import IBaseCap, CapBaseObject
|
from .base import IBaseCap, CapBaseObject, StringField, DateField
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ChatException', 'ICapChat']
|
__all__ = ['ChatException', 'ChatMessage', 'ICapChat']
|
||||||
|
|
||||||
|
|
||||||
class ChatException(Exception):
|
class ChatException(Exception):
|
||||||
pass
|
"""
|
||||||
|
Exception raised when there is a problem with the chat.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ChatMessage(CapBaseObject):
|
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):
|
def __init__(self, id_from, id_to, message, date=None):
|
||||||
CapBaseObject.__init__(self, '%s.%s' % (id_from, id_to))
|
CapBaseObject.__init__(self, '%s.%s' % (id_from, id_to))
|
||||||
self.add_field('id_from', basestring, id_from)
|
self.id_from = id_from
|
||||||
self.add_field('id_to', basestring, id_to)
|
self.id_to = id_to
|
||||||
self.add_field('message', basestring, message)
|
self.message = message
|
||||||
self.add_field('date', datetime.datetime, date)
|
self.date = date
|
||||||
|
|
||||||
if self.date is None:
|
if self.date is None:
|
||||||
self.date = datetime.datetime.utcnow()
|
self.date = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
|
||||||
class ICapChat(IBaseCap):
|
class ICapChat(IBaseCap):
|
||||||
|
"""
|
||||||
|
Websites with a chat system.
|
||||||
|
"""
|
||||||
def iter_chat_messages(self, _id=None):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def send_chat_message(self, _id, message):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,17 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
from weboob.tools.ordereddict import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ICapContact', 'Contact']
|
__all__ = ['ProfileNode', 'ContactPhoto', 'Contact', 'QueryError', 'Query', 'ICapContact']
|
||||||
|
|
||||||
|
|
||||||
class ProfileNode(object):
|
class ProfileNode(object):
|
||||||
|
"""
|
||||||
|
Node of a :class:`Contact` profile.
|
||||||
|
"""
|
||||||
HEAD = 0x01
|
HEAD = 0x01
|
||||||
SECTION = 0x02
|
SECTION = 0x02
|
||||||
|
|
||||||
|
|
@ -41,14 +44,19 @@ class ProfileNode(object):
|
||||||
|
|
||||||
|
|
||||||
class ContactPhoto(CapBaseObject):
|
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):
|
def __init__(self, name):
|
||||||
CapBaseObject.__init__(self, name)
|
CapBaseObject.__init__(self, name)
|
||||||
self.add_field('name', basestring, name)
|
self.name = 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)
|
|
||||||
|
|
||||||
def __iscomplete__(self):
|
def __iscomplete__(self):
|
||||||
return (self.data and (not self.thumbnail_url or self.thumbnail_data))
|
return (self.data and (not self.thumbnail_url or self.thumbnail_data))
|
||||||
|
|
@ -63,22 +71,35 @@ class ContactPhoto(CapBaseObject):
|
||||||
|
|
||||||
|
|
||||||
class Contact(CapBaseObject):
|
class Contact(CapBaseObject):
|
||||||
|
"""
|
||||||
|
A contact.
|
||||||
|
"""
|
||||||
STATUS_ONLINE = 0x001
|
STATUS_ONLINE = 0x001
|
||||||
STATUS_AWAY = 0x002
|
STATUS_AWAY = 0x002
|
||||||
STATUS_OFFLINE = 0x004
|
STATUS_OFFLINE = 0x004
|
||||||
STATUS_ALL = 0xfff
|
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):
|
def __init__(self, id, name, status):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', basestring, name)
|
self.name = name
|
||||||
self.add_field('status', int, status)
|
self.status = 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)
|
|
||||||
|
|
||||||
def set_photo(self, name, **kwargs):
|
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:
|
if not name in self.photos:
|
||||||
self.photos[name] = ContactPhoto(name)
|
self.photos[name] = ContactPhoto(name)
|
||||||
|
|
||||||
|
|
@ -88,13 +109,20 @@ class Contact(CapBaseObject):
|
||||||
|
|
||||||
|
|
||||||
class QueryError(Exception):
|
class QueryError(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when unable to send a query to a contact.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Query(CapBaseObject):
|
class Query(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Query to send to a contact.
|
||||||
|
"""
|
||||||
|
message = StringField('Message received')
|
||||||
|
|
||||||
def __init__(self, id, message):
|
def __init__(self, id, message):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('message', basestring, message)
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
class ICapContact(IBaseCap):
|
class ICapContact(IBaseCap):
|
||||||
|
|
@ -102,9 +130,11 @@ class ICapContact(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Iter contacts
|
Iter contacts
|
||||||
|
|
||||||
@param status get only contacts with the specified status
|
:param status: get only contacts with the specified status
|
||||||
@param ids if set, get the specified contacts
|
:type status: Contact.STATUS_*
|
||||||
@return iterator over the contacts found
|
:param ids: if set, get the specified contacts
|
||||||
|
:type ids: list[str]
|
||||||
|
:rtype: iter[:class:`Contact`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -116,8 +146,9 @@ class ICapContact(IBaseCap):
|
||||||
with the proper values, but it might be overloaded
|
with the proper values, but it might be overloaded
|
||||||
by backends.
|
by backends.
|
||||||
|
|
||||||
@param id the ID requested
|
:param id: the ID requested
|
||||||
@return the Contact object, or None if not found
|
:type id: str
|
||||||
|
:rtype: :class:`Contact` or None if not found
|
||||||
"""
|
"""
|
||||||
|
|
||||||
l = self.iter_contacts(ids=[id])
|
l = self.iter_contacts(ids=[id])
|
||||||
|
|
@ -130,9 +161,10 @@ class ICapContact(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Send a query to a contact
|
Send a query to a contact
|
||||||
|
|
||||||
@param id the ID of contact
|
:param id: the ID of contact
|
||||||
@return a Query object
|
:type id: str
|
||||||
@except QueryError
|
:rtype: :class:`Query`
|
||||||
|
:raises: :class:`QueryError`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -140,8 +172,9 @@ class ICapContact(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get personal notes about a contact
|
Get personal notes about a contact
|
||||||
|
|
||||||
@param id the ID of the contact
|
:param id: the ID of the contact
|
||||||
@return a unicode object
|
:type id: str
|
||||||
|
:rtype: unicode
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
@ -149,7 +182,8 @@ class ICapContact(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Set personal notes about a contact
|
Set personal notes about a contact
|
||||||
|
|
||||||
@param id the ID of the contact
|
:param id: the ID of the contact
|
||||||
@param notes the unicode object to save as notes
|
:type id: str
|
||||||
|
:returns: the unicode object to save as notes
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
|
||||||
|
|
@ -18,37 +18,74 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
from .base import IBaseCap, CapBaseObject
|
from .base import IBaseCap, CapBaseObject, StringField, DateField, Field
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
__all__ = ['Content', 'Revision', 'ICapContent']
|
||||||
|
|
||||||
|
|
||||||
class Content(CapBaseObject):
|
class Content(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Content object.
|
||||||
self.add_field('title', basestring)
|
"""
|
||||||
self.add_field('author', basestring)
|
title = StringField('Title of content')
|
||||||
self.add_field('content', basestring)
|
author = StringField('Original author of content')
|
||||||
self.add_field('revision', basestring)
|
content = StringField('Body')
|
||||||
|
revision = StringField('ID of revision')
|
||||||
|
|
||||||
class Revision(CapBaseObject):
|
class Revision(CapBaseObject):
|
||||||
def __init__(self, _id):
|
"""
|
||||||
CapBaseObject.__init__(self, _id)
|
Revision of a change on a content.
|
||||||
self.add_field('author', basestring)
|
"""
|
||||||
self.add_field('comment', basestring)
|
author = StringField('Author of revision')
|
||||||
self.add_field('revision', basestring)
|
comment = StringField('Comment log about revision')
|
||||||
self.add_field('timestamp', datetime)
|
timestamp = DateField('Date of revision')
|
||||||
self.add_field('minor', bool)
|
minor = Field('Is this change minor?', bool)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ICapContent(IBaseCap):
|
class ICapContent(IBaseCap):
|
||||||
def get_content(self, id, revision=None):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def iter_revisions(self, id, max_results=10):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def push_content(self, content, message=None, minor=False):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_content_preview(self, content):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,55 +18,103 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
from .base import IBaseCap, CapBaseObject, Field, StringField, DateField
|
||||||
|
|
||||||
from .base import IBaseCap, CapBaseObject
|
|
||||||
from .contact import Contact
|
from .contact import Contact
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ICapDating']
|
__all__ = ['OptimizationNotFound', 'Optimization', 'Event', 'ICapDating']
|
||||||
|
|
||||||
|
|
||||||
class OptimizationNotFound(Exception):
|
class OptimizationNotFound(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when an optimization is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Optimization(object):
|
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 = {}
|
CONFIG = {}
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
"""
|
||||||
|
Start optimization.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
"""
|
||||||
|
Stop optimization.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def is_running(self):
|
def is_running(self):
|
||||||
|
"""
|
||||||
|
Know if the optimization is currently running.
|
||||||
|
|
||||||
|
:rtype: bool
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self):
|
||||||
|
"""
|
||||||
|
Get config of this optimization.
|
||||||
|
|
||||||
|
:rtype: dict
|
||||||
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def set_config(self, params):
|
def set_config(self, params):
|
||||||
|
"""
|
||||||
|
Set config of this optimization.
|
||||||
|
|
||||||
|
:param params: parameters
|
||||||
|
:type params: dict
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class Event(CapBaseObject):
|
class Event(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
A dating event (for example a visite, a query received, etc.)
|
||||||
self.add_field('date', (datetime.datetime))
|
"""
|
||||||
self.add_field('contact', Contact)
|
date = DateField('Date of event')
|
||||||
self.add_field('type', basestring)
|
contact = Field('Contact related to this event', Contact)
|
||||||
self.add_field('message', basestring)
|
type = StringField('Type of event')
|
||||||
|
message = StringField('Message of the event')
|
||||||
|
|
||||||
class ICapDating(IBaseCap):
|
class ICapDating(IBaseCap):
|
||||||
|
"""
|
||||||
|
Capability for dating websites.
|
||||||
|
"""
|
||||||
def init_optimizations(self):
|
def init_optimizations(self):
|
||||||
|
"""
|
||||||
|
Initialization of optimizations.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def add_optimization(self, name, optim):
|
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)
|
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):
|
for attr_name in dir(self):
|
||||||
if not attr_name.startswith('OPTIM_'):
|
if not attr_name.startswith('OPTIM_'):
|
||||||
continue
|
continue
|
||||||
|
|
@ -77,6 +125,13 @@ class ICapDating(IBaseCap):
|
||||||
yield attr_name[6:], attr
|
yield attr_name[6:], attr
|
||||||
|
|
||||||
def get_optimization(self, optim):
|
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()
|
optim = optim.upper()
|
||||||
if not hasattr(self, 'OPTIM_%s' % optim):
|
if not hasattr(self, 'OPTIM_%s' % optim):
|
||||||
raise OptimizationNotFound()
|
raise OptimizationNotFound()
|
||||||
|
|
@ -84,4 +139,9 @@ class ICapDating(IBaseCap):
|
||||||
return getattr(self, 'OPTIM_%s' % optim)
|
return getattr(self, 'OPTIM_%s' % optim)
|
||||||
|
|
||||||
def iter_events(self):
|
def iter_events(self):
|
||||||
|
"""
|
||||||
|
Iter events.
|
||||||
|
|
||||||
|
:rtype: iter[:class:`Event`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -17,31 +17,39 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from weboob.tools.capabilities.thumbnail import Thumbnail
|
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):
|
class BaseGallery(CapBaseObject):
|
||||||
"""
|
"""
|
||||||
Represents a gallery.
|
Represents a gallery.
|
||||||
|
|
||||||
This object has to be inherited to specify how to calculate the URL of the gallery from its ID.
|
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,
|
def __init__(self, _id, title=NotLoaded, url=NotLoaded, cardinality=NotLoaded, date=NotLoaded,
|
||||||
rating=NotLoaded, rating_max=NotLoaded, thumbnail=NotLoaded, thumbnail_url=None, nsfw=False):
|
rating=NotLoaded, rating_max=NotLoaded, thumbnail=NotLoaded, thumbnail_url=None, nsfw=False):
|
||||||
CapBaseObject.__init__(self, unicode(_id))
|
CapBaseObject.__init__(self, unicode(_id))
|
||||||
|
|
||||||
self.add_field('title', basestring, title)
|
self.title = title
|
||||||
self.add_field('url', basestring, url)
|
self.url = url
|
||||||
self.add_field('description', basestring)
|
self.date = date
|
||||||
self.add_field('cardinality', int)
|
self.rating = rating
|
||||||
self.add_field('date', datetime, date)
|
self.rating_max = rating_max
|
||||||
self.add_field('rating', (int, long, float), rating)
|
self.thumbnail = thumbnail
|
||||||
self.add_field('rating_max', (int, long, float), rating_max)
|
|
||||||
self.add_field('thumbnail', Thumbnail, thumbnail)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def id2url(cls, _id):
|
def id2url(cls, _id):
|
||||||
|
|
@ -50,24 +58,39 @@ class BaseGallery(CapBaseObject):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def page_url(self):
|
def page_url(self):
|
||||||
|
"""
|
||||||
|
Get URL to page of this gallery.
|
||||||
|
"""
|
||||||
return self.id2url(self.id)
|
return self.id2url(self.id)
|
||||||
|
|
||||||
def iter_image(self):
|
def iter_image(self):
|
||||||
|
"""
|
||||||
|
Iter images.
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
class BaseImage(CapBaseObject):
|
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,
|
def __init__(self, _id, index=None, thumbnail=NotLoaded, url=NotLoaded,
|
||||||
ext=NotLoaded, gallery=None):
|
ext=NotLoaded, gallery=None):
|
||||||
|
|
||||||
CapBaseObject.__init__(self, unicode(_id))
|
CapBaseObject.__init__(self, unicode(_id))
|
||||||
|
|
||||||
self.add_field('index', int, index) # usually page number
|
self.index = index
|
||||||
self.add_field('thumbnail', Thumbnail, thumbnail)
|
self.thumbnail = thumbnail
|
||||||
self.add_field('url', basestring, url)
|
self.url = url
|
||||||
self.add_field('ext', basestring, ext)
|
self.ext = ext
|
||||||
self.add_field('data', str)
|
self.gallery = gallery
|
||||||
self.add_field('gallery', BaseGallery, gallery)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
@ -92,10 +115,15 @@ class ICapGallery(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Iter results of a search on a pattern.
|
Iter results of a search on a pattern.
|
||||||
|
|
||||||
@param pattern [str] pattern to search on
|
:param pattern: pattern to search on
|
||||||
@param sortby [enum] sort by...
|
:type pattern: str
|
||||||
@param nsfw [bool] include non-suitable for work videos if True
|
:param sortby: sort by...
|
||||||
@param max_results [int] maximum number of results to return
|
: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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -103,7 +131,8 @@ class ICapGallery(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get gallery from an ID.
|
Get gallery from an ID.
|
||||||
|
|
||||||
@param _id the gallery id. It can be a numeric ID, or a page url, or so.
|
:param _id: the gallery id. It can be a numeric ID, or a page url, or so.
|
||||||
@return a Gallery object
|
:type _id: str
|
||||||
|
:rtype: :class:`Gallery`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -16,39 +16,42 @@
|
||||||
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
|
||||||
|
|
||||||
from datetime import datetime
|
from .base import IBaseCap, CapBaseObject, StringField, FloatField, DateField
|
||||||
from .base import IBaseCap, CapBaseObject
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ICapWaterLevel']
|
__all__ = ['Gauge', 'GaugeMeasure', 'ICapWaterLevel']
|
||||||
|
|
||||||
|
|
||||||
class Gauge(CapBaseObject):
|
class Gauge(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Gauge class.
|
||||||
|
"""
|
||||||
self.add_field('name', basestring)
|
name = StringField('Name of gauge')
|
||||||
self.add_field('river', basestring)
|
river = StringField('What river')
|
||||||
self.add_field('level', float)
|
level = FloatField('Level of gauge')
|
||||||
self.add_field('flow', float)
|
flow = FloatField('Flow of gauge')
|
||||||
self.add_field('lastdate', datetime)
|
lastdate = DateField('Last measure')
|
||||||
self.add_field('forecast', basestring)
|
forecast = StringField('Forecast')
|
||||||
|
|
||||||
class GaugeMeasure(CapBaseObject):
|
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):
|
def __init__(self):
|
||||||
CapBaseObject.__init__(self, None)
|
CapBaseObject.__init__(self, None)
|
||||||
|
|
||||||
self.add_field('level', float)
|
|
||||||
self.add_field('flow', float)
|
|
||||||
self.add_field('date', datetime)
|
|
||||||
|
|
||||||
class ICapWaterLevel(IBaseCap):
|
class ICapWaterLevel(IBaseCap):
|
||||||
def iter_gauge_history(self, id):
|
def iter_gauge_history(self, id):
|
||||||
"""
|
"""
|
||||||
Get history of a gauge.
|
Get history of a gauge.
|
||||||
|
|
||||||
@param id [str] ID of the river
|
:param id: ID of the river
|
||||||
@return [iter(GaugeMeasure)]
|
:type id: str
|
||||||
|
:rtype: iter[:class:`GaugeMeasure`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -56,8 +59,9 @@ class ICapWaterLevel(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get last measure of the gauge.
|
Get last measure of the gauge.
|
||||||
|
|
||||||
@param id [str] ID of the gauge.
|
:param id: ID of the gauge
|
||||||
@return [GaugeMeasure]
|
:type id: str
|
||||||
|
:rtype: :class:`GaugeMeasure`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -65,7 +69,8 @@ class ICapWaterLevel(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Iter gauges.
|
Iter gauges.
|
||||||
|
|
||||||
@param pattern [str] if specified, used to search gauges
|
:param pattern: if specified, used to search gauges
|
||||||
@return [iter(Gauge)]
|
:type pattern: str
|
||||||
|
:rtype: iter[:class:`Gauge`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,27 +18,40 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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']
|
__all__ = ['IpLocation', 'ICapGeolocIp']
|
||||||
|
|
||||||
|
|
||||||
class IpLocation(CapBaseObject):
|
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):
|
def __init__(self, ipaddr):
|
||||||
CapBaseObject.__init__(self, ipaddr)
|
CapBaseObject.__init__(self, ipaddr)
|
||||||
|
|
||||||
self.ipaddr = 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):
|
class ICapGeolocIp(IBaseCap):
|
||||||
|
"""
|
||||||
|
Access information about IP addresses database.
|
||||||
|
"""
|
||||||
def get_location(self, ipaddr):
|
def get_location(self, ipaddr):
|
||||||
|
"""
|
||||||
|
Get location of an IP address.
|
||||||
|
|
||||||
|
:param ipaddr: IP address
|
||||||
|
:type ipaddr: str
|
||||||
|
:rtype: :class:`IpLocation`
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,23 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
from datetime import date
|
from .base import IBaseCap, CapBaseObject, Field, IntField, FloatField, \
|
||||||
|
StringField, BytesField, DateField
|
||||||
from .base import IBaseCap, CapBaseObject
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ICapHousing']
|
__all__ = ['HousingPhoto', 'Housing', 'Query', 'City', 'ICapHousing']
|
||||||
|
|
||||||
|
|
||||||
class HousingPhoto(CapBaseObject):
|
class HousingPhoto(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Photo of a housing.
|
||||||
|
"""
|
||||||
|
url = StringField('Direct URL to photo')
|
||||||
|
data = BytesField('Data of photo')
|
||||||
|
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
CapBaseObject.__init__(self, url.split('/')[-1])
|
CapBaseObject.__init__(self, url.split('/')[-1])
|
||||||
self.add_field('url', basestring, url)
|
self.url = url
|
||||||
self.add_field('data', str)
|
|
||||||
|
|
||||||
def __iscomplete__(self):
|
def __iscomplete__(self):
|
||||||
return self.data
|
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)
|
return u'<HousingPhoto "%s" data=%do>' % (self.id, len(self.data) if self.data else 0)
|
||||||
|
|
||||||
class Housing(CapBaseObject):
|
class Housing(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Content of a housing.
|
||||||
self.add_field('title', basestring)
|
"""
|
||||||
self.add_field('area', (int,float))
|
title = StringField('Title of housing')
|
||||||
self.add_field('cost', (int,float))
|
area = FloatField('Area of housing, in m2')
|
||||||
self.add_field('currency', basestring)
|
cost = FloatField('Cost of housing')
|
||||||
self.add_field('date', date)
|
currency = StringField('Currency of cost')
|
||||||
self.add_field('location', basestring)
|
date = DateField('Date when the housing has been published')
|
||||||
self.add_field('station', basestring)
|
location = StringField('Location of housing')
|
||||||
self.add_field('text', basestring)
|
station = StringField('What metro/bus station next to housing')
|
||||||
self.add_field('phone', basestring)
|
text = StringField('Text of the housing')
|
||||||
self.add_field('photos', list)
|
phone = StringField('Phone number to contact')
|
||||||
self.add_field('details', dict)
|
photos = Field('List of photos', list)
|
||||||
|
details = Field('Key/values of details', dict)
|
||||||
|
|
||||||
class Query(CapBaseObject):
|
class Query(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Query to find housings.
|
||||||
|
"""
|
||||||
TYPE_RENT = 0
|
TYPE_RENT = 0
|
||||||
TYPE_SALE = 1
|
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):
|
def __init__(self):
|
||||||
CapBaseObject.__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):
|
class City(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
City.
|
||||||
self.add_field('name', basestring)
|
"""
|
||||||
|
name = StringField('Name of city')
|
||||||
|
|
||||||
class ICapHousing(IBaseCap):
|
class ICapHousing(IBaseCap):
|
||||||
|
"""
|
||||||
|
Capability of websites to search housings.
|
||||||
|
"""
|
||||||
def search_housings(self, query):
|
def search_housings(self, query):
|
||||||
|
"""
|
||||||
|
Search housings.
|
||||||
|
|
||||||
|
:param query: search query
|
||||||
|
:type query: :class:`Query`
|
||||||
|
:rtype: iter[:class:`Housing`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_housing(self, housing):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def search_city(self, pattern):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -17,40 +17,59 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from datetime import datetime, date
|
|
||||||
|
|
||||||
from .collection import ICapCollection
|
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):
|
class Book(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Describes a book.
|
||||||
self.add_field('name', basestring)
|
"""
|
||||||
self.add_field('author', basestring)
|
name = StringField('Name of the book')
|
||||||
self.add_field('location', basestring)
|
author = StringField('Author of the book')
|
||||||
self.add_field('date', (datetime, date)) # which may be the due date
|
location = StringField('Location')
|
||||||
self.add_field('late', bool)
|
date = DateField('The due date')
|
||||||
|
late = Field('Are you late?', bool)
|
||||||
|
|
||||||
class Renew(CapBaseObject):
|
class Renew(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
A renew message.
|
||||||
self.add_field('message', basestring)
|
"""
|
||||||
|
message = StringField('Message')
|
||||||
|
|
||||||
class ICapBook(ICapCollection):
|
class ICapBook(ICapCollection):
|
||||||
|
"""
|
||||||
|
Library websites.
|
||||||
|
"""
|
||||||
def iter_resources(self, objs, split_path):
|
def iter_resources(self, objs, split_path):
|
||||||
|
"""
|
||||||
|
Iter resources. It retuns :func:`iter_books`.
|
||||||
|
"""
|
||||||
if Book in objs:
|
if Book in objs:
|
||||||
self._restrict_level(split_path)
|
self._restrict_level(split_path)
|
||||||
|
|
||||||
return self.iter_books()
|
return self.iter_books()
|
||||||
|
|
||||||
def iter_books(self, pattern):
|
def iter_books(self, pattern):
|
||||||
|
"""
|
||||||
|
Iter books from a pattern.
|
||||||
|
|
||||||
|
:param pattern: pattern to search
|
||||||
|
:type pattern: str
|
||||||
|
:rtype: iter[:class:`Book`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_book(self, _id):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_booked(self, _id):
|
def get_booked(self, _id):
|
||||||
|
|
|
||||||
|
|
@ -21,18 +21,42 @@
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
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_HTML = 0x001 # The content is HTML formatted
|
||||||
IS_UNREAD = 0x002 # The message is unread
|
IS_UNREAD = 0x002 # The message is unread
|
||||||
IS_RECEIVED = 0x004 # The receiver has read this message
|
IS_RECEIVED = 0x004 # The receiver has read this message
|
||||||
IS_NOT_RECEIVED = 0x008 # The receiver has not 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,
|
def __init__(self, thread, id,
|
||||||
title=NotLoaded,
|
title=NotLoaded,
|
||||||
sender=NotLoaded,
|
sender=NotLoaded,
|
||||||
|
|
@ -45,16 +69,14 @@ class Message(CapBaseObject):
|
||||||
flags=0):
|
flags=0):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
assert thread is not None
|
assert thread is not None
|
||||||
self.add_field('thread', Thread, thread)
|
self.thread = thread
|
||||||
self.add_field('title', basestring, title)
|
self.title = title
|
||||||
self.add_field('sender', basestring, sender)
|
self.sender = sender
|
||||||
self.add_field('receivers', list, receivers)
|
self.receivers = receivers
|
||||||
self.add_field('date', (datetime.datetime, datetime.date), date)
|
self.content = content
|
||||||
self.add_field('parent', Message, parent)
|
self.signature = signature
|
||||||
self.add_field('content', basestring, content)
|
self.children = children
|
||||||
self.add_field('signature', basestring, signature)
|
self.flags = flags
|
||||||
self.add_field('children', list, children)
|
|
||||||
self.add_field('flags', int, flags)
|
|
||||||
|
|
||||||
if date is None:
|
if date is None:
|
||||||
date = datetime.datetime.utcnow()
|
date = datetime.datetime.utcnow()
|
||||||
|
|
@ -68,14 +90,23 @@ class Message(CapBaseObject):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_int(self):
|
def date_int(self):
|
||||||
|
"""
|
||||||
|
Date of message as an integer.
|
||||||
|
"""
|
||||||
return int(time.strftime('%Y%m%d%H%M%S', self.date.timetuple()))
|
return int(time.strftime('%Y%m%d%H%M%S', self.date.timetuple()))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_id(self):
|
def full_id(self):
|
||||||
|
"""
|
||||||
|
Full ID of message (in form '**THREAD_ID.MESSAGE_ID**')
|
||||||
|
"""
|
||||||
return '%s.%s' % (self.thread.id, self.id)
|
return '%s.%s' % (self.thread.id, self.id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_parent_id(self):
|
def full_parent_id(self):
|
||||||
|
"""
|
||||||
|
Get the full ID of the parent message (in form '**THREAD_ID.MESSAGE_ID**').
|
||||||
|
"""
|
||||||
if self.parent:
|
if self.parent:
|
||||||
return self.parent.full_id
|
return self.parent.full_id
|
||||||
elif self._parent_id is None:
|
elif self._parent_id is None:
|
||||||
|
|
@ -96,18 +127,24 @@ class Message(CapBaseObject):
|
||||||
return '<Message id=%r title=%r date=%r from=%r>' % (
|
return '<Message id=%r title=%r date=%r from=%r>' % (
|
||||||
self.full_id, self.title, self.date, self.sender)
|
self.full_id, self.title, self.date, self.sender)
|
||||||
|
|
||||||
class Thread(CapBaseObject):
|
class Thread(_Thread):
|
||||||
|
"""
|
||||||
|
Thread containing messages.
|
||||||
|
"""
|
||||||
IS_THREADS = 0x001
|
IS_THREADS = 0x001
|
||||||
IS_DISCUSSION = 0x002
|
IS_DISCUSSION = 0x002
|
||||||
|
|
||||||
def __init__(self, id):
|
root = Field('Root message', Message)
|
||||||
CapBaseObject.__init__(self, id)
|
title = StringField('Title of thread')
|
||||||
self.add_field('root', Message)
|
date = DateField('Date of thread')
|
||||||
self.add_field('title', basestring)
|
flags = IntField('Flags (IS_* constants)', default=0)
|
||||||
self.add_field('date', (datetime.datetime, datetime.date))
|
|
||||||
self.add_field('flags', int, self.IS_THREADS)
|
|
||||||
|
|
||||||
def iter_all_messages(self):
|
def iter_all_messages(self):
|
||||||
|
"""
|
||||||
|
Iter all messages of the thread.
|
||||||
|
|
||||||
|
:rtype: iter[:class:`Message`]
|
||||||
|
"""
|
||||||
if self.root:
|
if self.root:
|
||||||
yield self.root
|
yield self.root
|
||||||
for m in self._iter_all_messages(self.root):
|
for m in self._iter_all_messages(self.root):
|
||||||
|
|
@ -122,11 +159,14 @@ class Thread(CapBaseObject):
|
||||||
|
|
||||||
|
|
||||||
class ICapMessages(IBaseCap):
|
class ICapMessages(IBaseCap):
|
||||||
|
"""
|
||||||
|
Capability to read messages.
|
||||||
|
"""
|
||||||
def iter_threads(self):
|
def iter_threads(self):
|
||||||
"""
|
"""
|
||||||
Iterates on threads, from newers to olders.
|
Iterates on threads, from newers to olders.
|
||||||
|
|
||||||
@return [iter] Thread objects
|
:rtype: iter[:class:`Thread`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -134,7 +174,7 @@ class ICapMessages(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get a specific thread.
|
Get a specific thread.
|
||||||
|
|
||||||
@return [Thread] the Thread object
|
:rtype: :class:`Thread`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -142,7 +182,7 @@ class ICapMessages(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Iterates on messages which hasn't been marked as read.
|
Iterates on messages which hasn't been marked as read.
|
||||||
|
|
||||||
@return [iter] Message objects
|
:rtype: iter[:class:`Message`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -150,19 +190,26 @@ class ICapMessages(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Set a message as read.
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
class CantSendMessage(Exception):
|
class CantSendMessage(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when a message can't be send.
|
||||||
|
"""
|
||||||
|
|
||||||
class ICapMessagesPost(IBaseCap):
|
class ICapMessagesPost(IBaseCap):
|
||||||
|
"""
|
||||||
|
This capability allow user to send a message.
|
||||||
|
"""
|
||||||
def post_message(self, message):
|
def post_message(self, message):
|
||||||
"""
|
"""
|
||||||
Post a message.
|
Post a message.
|
||||||
|
|
||||||
@param message Message object
|
:param message: message to send
|
||||||
@return
|
:type message: :class:`Message`
|
||||||
|
:raises: :class:`CantSendMessage`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,27 +18,34 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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']
|
__all__ = ['PasteNotFound', 'BasePaste', 'ICapPaste']
|
||||||
|
|
||||||
|
|
||||||
class PasteNotFound(Exception):
|
class PasteNotFound(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when a paste is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
class BasePaste(CapBaseObject):
|
class BasePaste(CapBaseObject):
|
||||||
"""
|
"""
|
||||||
Represents a pasted text.
|
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,
|
def __init__(self, _id, title=NotLoaded, language=NotLoaded, contents=NotLoaded,
|
||||||
public=NotLoaded):
|
public=NotLoaded):
|
||||||
CapBaseObject.__init__(self, unicode(_id))
|
CapBaseObject.__init__(self, unicode(_id))
|
||||||
|
|
||||||
self.add_field('title', basestring, title)
|
self.title = title
|
||||||
self.add_field('language', basestring, language)
|
self.language = language
|
||||||
self.add_field('contents', basestring, contents)
|
self.contents = contents
|
||||||
self.add_field('public', bool, public)
|
self.public = public
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def id2url(cls, _id):
|
def id2url(cls, _id):
|
||||||
|
|
@ -47,6 +54,9 @@ class BasePaste(CapBaseObject):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def page_url(self):
|
def page_url(self):
|
||||||
|
"""
|
||||||
|
Get URL to page of this paste.
|
||||||
|
"""
|
||||||
return self.id2url(self.id)
|
return self.id2url(self.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -60,7 +70,7 @@ class ICapPaste(IBaseCap):
|
||||||
Get a new paste object for posting it with the backend.
|
Get a new paste object for posting it with the backend.
|
||||||
The parameters should be passed to the object init.
|
The parameters should be passed to the object init.
|
||||||
|
|
||||||
@return a Paste object
|
:rtype: :class:`BasePaste`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -79,7 +89,8 @@ class ICapPaste(IBaseCap):
|
||||||
A score of 1 means the backend is suitable.
|
A score of 1 means the backend is suitable.
|
||||||
Higher scores means it is more suitable than others with a lower score.
|
Higher scores means it is more suitable than others with a lower score.
|
||||||
|
|
||||||
@return int Score
|
:rtype: int
|
||||||
|
:returns: score
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -87,8 +98,10 @@ class ICapPaste(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get a Paste from an ID or URL.
|
Get a Paste from an ID or URL.
|
||||||
|
|
||||||
@param _id the paste id. It can be an ID or a page URL.
|
:param _id: the paste id. It can be an ID or a page URL.
|
||||||
@return a Paste object
|
:type _id: str
|
||||||
|
:rtype: :class:`BasePaste`
|
||||||
|
:raises: :class:`PasteNotFound`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -96,7 +109,7 @@ class ICapPaste(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Post a paste.
|
Post a paste.
|
||||||
|
|
||||||
@param paste Paste object
|
:param paste: a Paste object
|
||||||
@return
|
:type paste: :class:`BasePaste`
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,17 +18,18 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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']
|
__all__ = ['Emission', 'Stream', 'Radio', 'ICapRadio']
|
||||||
|
|
||||||
|
|
||||||
class Emission(CapBaseObject):
|
class Emission(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Emission of a radio.
|
||||||
self.add_field('artist', unicode)
|
"""
|
||||||
self.add_field('title', unicode)
|
artist = StringField('Name of artist')
|
||||||
|
title = StringField('Title of song or emission')
|
||||||
|
|
||||||
def __iscomplete__(self):
|
def __iscomplete__(self):
|
||||||
# This volatile information may be reloaded everytimes.
|
# This volatile information may be reloaded everytimes.
|
||||||
|
|
@ -41,10 +42,11 @@ class Emission(CapBaseObject):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
class Stream(CapBaseObject):
|
class Stream(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Stream of a radio.
|
||||||
self.add_field('title', unicode)
|
"""
|
||||||
self.add_field('url', unicode)
|
title = StringField('Title of stream')
|
||||||
|
url = StringField('Direct URL to the stream')
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return u'%s (%s)' % (self.title, self.url)
|
return u'%s (%s)' % (self.title, self.url)
|
||||||
|
|
@ -53,16 +55,34 @@ class Stream(CapBaseObject):
|
||||||
return self.__unicode__()
|
return self.__unicode__()
|
||||||
|
|
||||||
class Radio(CapBaseObject):
|
class Radio(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
Radio object.
|
||||||
self.add_field('title', unicode)
|
"""
|
||||||
self.add_field('description', unicode)
|
title = StringField('Title of radio')
|
||||||
self.add_field('current', Emission)
|
description = StringField('Description of radio')
|
||||||
self.add_field('streams', list)
|
current = Field('Current emission', Emission)
|
||||||
|
streams = Field('List of streams', list)
|
||||||
|
|
||||||
class ICapRadio(IBaseCap):
|
class ICapRadio(IBaseCap):
|
||||||
|
"""
|
||||||
|
Capability of radio websites.
|
||||||
|
"""
|
||||||
def iter_radios_search(self, pattern):
|
def iter_radios_search(self, pattern):
|
||||||
|
"""
|
||||||
|
Search a radio.
|
||||||
|
|
||||||
|
:param pattern: pattern to search
|
||||||
|
:type pattern: str
|
||||||
|
:rtype: iter[:class:`Radio`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_radio(self, id):
|
def get_radio(self, id):
|
||||||
|
"""
|
||||||
|
Get a radio from an ID.
|
||||||
|
|
||||||
|
:param id: ID of radio
|
||||||
|
:type id: str
|
||||||
|
:rtype: :class:`Radio`
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -17,41 +17,72 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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):
|
class MagnetOnly(Exception):
|
||||||
|
"""
|
||||||
|
Raised when trying to get URL to torrent but only magnet is available.
|
||||||
|
"""
|
||||||
def __init__(self, magnet):
|
def __init__(self, magnet):
|
||||||
self.magnet = magnet
|
self.magnet = magnet
|
||||||
Exception.__init__(self, 'Only magnet URL is available')
|
Exception.__init__(self, 'Only magnet URL is available')
|
||||||
|
|
||||||
|
|
||||||
class Torrent(CapBaseObject):
|
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):
|
def __init__(self, id, name):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', basestring, name)
|
self.name = 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
|
|
||||||
|
|
||||||
|
|
||||||
class ICapTorrent(IBaseCap):
|
class ICapTorrent(IBaseCap):
|
||||||
|
"""
|
||||||
|
Torrent trackers.
|
||||||
|
"""
|
||||||
def iter_torrents(self, pattern):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_torrent(self, _id):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_torrent_file(self, _id):
|
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()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -18,76 +18,98 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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):
|
class Station(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Describes a station.
|
||||||
|
"""
|
||||||
|
name = StringField('Name of station')
|
||||||
|
|
||||||
def __init__(self, id, name):
|
def __init__(self, id, name):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', basestring, name)
|
self.name = name
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Station id=%r name=%r>" % (self.id, self.name)
|
return "<Station id=%r name=%r>" % (self.id, self.name)
|
||||||
|
|
||||||
class Departure(CapBaseObject):
|
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):
|
def __init__(self, id, _type, _time):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
|
|
||||||
self.add_field('type', basestring, _type)
|
self.type = _type
|
||||||
self.add_field('time', datetime, _time)
|
self.time = _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)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return u"<Departure id=%r type=%r time=%r departure=%r arrival=%r>" % (
|
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)
|
self.id, self.type, self.time.strftime('%H:%M'), self.departure_station, self.arrival_station)
|
||||||
|
|
||||||
class RoadStep(CapBaseObject):
|
class RoadStep(CapBaseObject):
|
||||||
def __init__(self, id):
|
"""
|
||||||
CapBaseObject.__init__(self, id)
|
A step on a roadmap.
|
||||||
|
"""
|
||||||
self.add_field('line', basestring)
|
line = StringField('When line')
|
||||||
self.add_field('start_time', time)
|
start_time = TimeField('Start of step')
|
||||||
self.add_field('end_time', time)
|
end_time = TimeField('End of step')
|
||||||
self.add_field('departure', unicode)
|
departure = StringField('Departure station')
|
||||||
self.add_field('arrival', unicode)
|
arrival = StringField('Arrival station')
|
||||||
self.add_field('duration', timedelta)
|
duration = DeltaField('Duration of this step')
|
||||||
|
|
||||||
class RoadmapError(Exception):
|
class RoadmapError(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when the roadmap is unable to be calculated.
|
||||||
|
"""
|
||||||
|
|
||||||
class RoadmapFilters(CapBaseObject):
|
class RoadmapFilters(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Filters to get a roadmap.
|
||||||
|
"""
|
||||||
|
departure_time = DateField('Wanted departure time')
|
||||||
|
arrival_time = DateField('Wanted arrival time')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
CapBaseObject.__init__(self, '')
|
CapBaseObject.__init__(self, '')
|
||||||
|
|
||||||
self.add_field('departure_time', datetime)
|
|
||||||
self.add_field('arrival_time', datetime)
|
|
||||||
|
|
||||||
class ICapTravel(IBaseCap):
|
class ICapTravel(IBaseCap):
|
||||||
|
"""
|
||||||
|
Travel websites.
|
||||||
|
"""
|
||||||
def iter_station_search(self, pattern):
|
def iter_station_search(self, pattern):
|
||||||
"""
|
"""
|
||||||
Iterates on search results of stations.
|
Iterates on search results of stations.
|
||||||
|
|
||||||
@param pattern [str] the search pattern
|
:param pattern: the search pattern
|
||||||
@return [iter] the of Station objects
|
:type pattern: str
|
||||||
|
:rtype: iter[:class:`Station`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def iter_station_departures(self, station_id, arrival_id):
|
def iter_station_departures(self, station_id, arrival_id=None):
|
||||||
"""
|
"""
|
||||||
Iterate on departures.
|
Iterate on departures.
|
||||||
|
|
||||||
@param station_id [id] the station id
|
:param station_id: the station ID
|
||||||
@param arrival_id [id] optionnal arrival station id
|
:type station_id: str
|
||||||
@return [iter] result of Departure objects
|
:param arrival_id: optionnal arrival station ID
|
||||||
|
:type arrival_id: str
|
||||||
|
:rtype: iter[:class:`Departure`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -95,9 +117,12 @@ class ICapTravel(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get a roadmap.
|
Get a roadmap.
|
||||||
|
|
||||||
@param departure [str] name of departure station
|
:param departure: name of departure station
|
||||||
@param arrival [str] name of arrival station
|
:type departure: str
|
||||||
@param filters [RoadmapFilters] filters on search
|
:param arrival: name of arrival station
|
||||||
@return [iter(RoadStep)] steps of roadmap
|
:type arrival: str
|
||||||
|
:param filters: filters on search
|
||||||
|
:type filters: :class:`RoadmapFilters`
|
||||||
|
:rtype: iter[:class:`RoadStep`]
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- 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.
|
# This file is part of weboob.
|
||||||
#
|
#
|
||||||
|
|
@ -18,34 +18,33 @@
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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
|
from weboob.tools.capabilities.thumbnail import Thumbnail
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['BaseVideo', 'ICapVideo']
|
__all__ = ['BaseVideo', 'ICapVideo']
|
||||||
|
|
||||||
|
|
||||||
class BaseVideo(CapBaseObject):
|
class BaseVideo(CapBaseObject):
|
||||||
"""
|
"""
|
||||||
Represents a video.
|
Represents a video.
|
||||||
|
|
||||||
This object has to be inherited to specify how to calculate the URL of the video from its ID.
|
This object has to be inherited to specify how to calculate the URL of the video from its ID.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, _id):
|
title = StringField('Title of video')
|
||||||
CapBaseObject.__init__(self, unicode(_id))
|
url = StringField('URL to the video file')
|
||||||
|
ext = StringField('Extension of video')
|
||||||
self.add_field('title', basestring)
|
author = StringField('Author of video')
|
||||||
self.add_field('url', basestring)
|
description = StringField('Description of video')
|
||||||
self.add_field('ext', basestring)
|
duration = Field('Duration of video', int, long, timedelta)
|
||||||
self.add_field('author', basestring)
|
date = DateField('Date when the video has been published')
|
||||||
self.add_field('description', basestring)
|
rating = Field('Rating of video', int, long, float, default=NotAvailable)
|
||||||
self.add_field('duration', (int,long,timedelta))
|
rating_max = Field('Max rating', int, long, float, default=NotAvailable)
|
||||||
self.add_field('date', datetime)
|
thumbnail = Field('Thumbnail of video', Thumbnail)
|
||||||
self.add_field('rating', (int,long,float), NotAvailable)
|
nsfw = Field('Is this video Not Safe For Work', bool, default=False)
|
||||||
self.add_field('rating_max', (int,long,float), NotAvailable)
|
|
||||||
self.add_field('thumbnail', Thumbnail)
|
|
||||||
self.add_field('nsfw', bool, False)
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def id2url(cls, _id):
|
def id2url(cls, _id):
|
||||||
|
|
@ -54,6 +53,9 @@ class BaseVideo(CapBaseObject):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def page_url(self):
|
def page_url(self):
|
||||||
|
"""
|
||||||
|
Get page URL of the video.
|
||||||
|
"""
|
||||||
return self.id2url(self.id)
|
return self.id2url(self.id)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -70,10 +72,14 @@ class ICapVideo(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Iter results of a search on a pattern.
|
Iter results of a search on a pattern.
|
||||||
|
|
||||||
@param pattern [str] pattern to search on
|
:param pattern: pattern to search on
|
||||||
@param sortby [enum] sort by...
|
:type pattern: str
|
||||||
@param nsfw [bool] include non-suitable for work videos if True
|
:param sortby: sort by... (use SEARCH_* constants)
|
||||||
@param max_results [int] maximum number of results to return
|
: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()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
@ -81,7 +87,8 @@ class ICapVideo(IBaseCap):
|
||||||
"""
|
"""
|
||||||
Get a Video from an ID.
|
Get a Video from an ID.
|
||||||
|
|
||||||
@param _id the video id. It can be a numeric ID, or a page url, or so.
|
:param _id: the video id. It can be a numeric ID, or a page url
|
||||||
@return a Video object
|
:type _id: str
|
||||||
|
:rtype: :class:`BaseVideo` or None is fot found.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -20,43 +20,89 @@
|
||||||
|
|
||||||
from datetime import datetime
|
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):
|
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):
|
def __init__(self, date, low, high, text, unit):
|
||||||
CapBaseObject.__init__(self, date)
|
CapBaseObject.__init__(self, unicode(date))
|
||||||
self.add_field('date', (basestring,datetime), date)
|
self.date = date
|
||||||
self.add_field('low', (int,float), low)
|
self.low = low
|
||||||
self.add_field('high', (int,float), high)
|
self.high = high
|
||||||
self.add_field('text', basestring, text)
|
self.text = text
|
||||||
self.add_field('unit', basestring, unit)
|
self.unit = unit
|
||||||
|
|
||||||
class Current(CapBaseObject):
|
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):
|
def __init__(self, date, temp, text, unit):
|
||||||
CapBaseObject.__init__(self, date)
|
CapBaseObject.__init__(self, unicode(date))
|
||||||
self.add_field('date', (basestring,datetime), date)
|
self.date = date
|
||||||
self.add_field('text', basestring, text)
|
self.text = text
|
||||||
self.add_field('temp', (int,float), temp)
|
self.temp = temp
|
||||||
self.add_field('unit', basestring, unit)
|
self.unit = unit
|
||||||
|
|
||||||
class City(CapBaseObject):
|
class City(CapBaseObject):
|
||||||
|
"""
|
||||||
|
City where to find weather.
|
||||||
|
"""
|
||||||
|
name = StringField('Name of city')
|
||||||
|
|
||||||
def __init__(self, id, name):
|
def __init__(self, id, name):
|
||||||
CapBaseObject.__init__(self, id)
|
CapBaseObject.__init__(self, id)
|
||||||
self.add_field('name', basestring, name)
|
self.name = name
|
||||||
|
|
||||||
class CityNotFound(Exception):
|
class CityNotFound(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when a city is not found.
|
||||||
|
"""
|
||||||
|
|
||||||
class ICapWeather(IBaseCap):
|
class ICapWeather(IBaseCap):
|
||||||
|
"""
|
||||||
|
Capability for weather websites.
|
||||||
|
"""
|
||||||
def iter_city_search(self, pattern):
|
def iter_city_search(self, pattern):
|
||||||
|
"""
|
||||||
|
Look for a city.
|
||||||
|
|
||||||
|
:param pattern: pattern to search
|
||||||
|
:type pattern: str
|
||||||
|
:rtype: iter[:class:`City`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def get_current(self, city_id):
|
def get_current(self, city_id):
|
||||||
|
"""
|
||||||
|
Get current weather.
|
||||||
|
|
||||||
|
:param city_id: ID of the city
|
||||||
|
:rtype: :class:`Current`
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def iter_forecast(self, city_id):
|
def iter_forecast(self, city_id):
|
||||||
|
"""
|
||||||
|
Iter forecasts of a city.
|
||||||
|
|
||||||
|
:param city_id: ID of the city
|
||||||
|
:rtype: iter[:class:`Forecast`]
|
||||||
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,19 @@ __all__ = ['Weboob']
|
||||||
|
|
||||||
|
|
||||||
class Weboob(object):
|
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'
|
VERSION = '0.c'
|
||||||
BACKENDS_FILENAME = 'backends'
|
BACKENDS_FILENAME = 'backends'
|
||||||
|
|
||||||
|
|
@ -64,8 +77,8 @@ class Weboob(object):
|
||||||
if os.path.isdir(old_workdir):
|
if os.path.isdir(old_workdir):
|
||||||
self.logger.warning('You are using "%s" as working directory. Files are moved into %s and %s.'
|
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))
|
% (old_workdir, xdg_config_home, xdg_data_home))
|
||||||
self.create_dir(xdg_config_home)
|
self._create_dir(xdg_config_home)
|
||||||
self.create_dir(xdg_data_home)
|
self._create_dir(xdg_data_home)
|
||||||
for f in os.listdir(old_workdir):
|
for f in os.listdir(old_workdir):
|
||||||
if f in Repositories.SHARE_DIRS:
|
if f in Repositories.SHARE_DIRS:
|
||||||
dest = xdg_data_home
|
dest = xdg_data_home
|
||||||
|
|
@ -77,7 +90,7 @@ class Weboob(object):
|
||||||
datadir = xdg_data_home
|
datadir = xdg_data_home
|
||||||
|
|
||||||
self.workdir = os.path.realpath(workdir)
|
self.workdir = os.path.realpath(workdir)
|
||||||
self.create_dir(workdir)
|
self._create_dir(workdir)
|
||||||
|
|
||||||
# Repositories management
|
# Repositories management
|
||||||
self.repositories = Repositories(workdir, datadir, self.VERSION)
|
self.repositories = Repositories(workdir, datadir, self.VERSION)
|
||||||
|
|
@ -95,7 +108,7 @@ class Weboob(object):
|
||||||
# Storage
|
# Storage
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
|
|
||||||
def create_dir(self, name):
|
def _create_dir(self, name):
|
||||||
if not os.path.exists(name):
|
if not os.path.exists(name):
|
||||||
os.makedirs(name)
|
os.makedirs(name)
|
||||||
elif not os.path.isdir(name):
|
elif not os.path.isdir(name):
|
||||||
|
|
@ -105,11 +118,15 @@ class Weboob(object):
|
||||||
self.deinit()
|
self.deinit()
|
||||||
|
|
||||||
def deinit(self):
|
def deinit(self):
|
||||||
|
"""
|
||||||
|
Call this method when you stop using Weboob, to
|
||||||
|
properly unload all correctly.
|
||||||
|
"""
|
||||||
self.unload_backends()
|
self.unload_backends()
|
||||||
|
|
||||||
def update(self, progress=IProgress()):
|
def update(self, progress=IProgress()):
|
||||||
"""
|
"""
|
||||||
Update modules.
|
Update modules from repositories.
|
||||||
"""
|
"""
|
||||||
self.repositories.update(progress)
|
self.repositories.update(progress)
|
||||||
|
|
||||||
|
|
@ -120,13 +137,27 @@ class Weboob(object):
|
||||||
self.repositories.install(minfo, progress)
|
self.repositories.install(minfo, progress)
|
||||||
|
|
||||||
class LoadError(Exception):
|
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):
|
def __init__(self, backend_name, exception):
|
||||||
Exception.__init__(self, unicode(exception))
|
Exception.__init__(self, unicode(exception))
|
||||||
self.backend_name = backend_name
|
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)
|
minfo = self.repositories.get_module_info(module_name)
|
||||||
if minfo is None:
|
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):
|
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 caps: load backends which implement all of specified caps
|
||||||
@param names [tuple(unicode)] load backends with instance name in list
|
:type caps: tuple[:class:`weboob.capabilities.base.ICapBase`]
|
||||||
@param modules [tuple(unicode)] load backends which module is in list
|
:param names: load backends with instance name in list
|
||||||
@param storage [IStorage] use the storage if specified
|
:type names: tuple[:class:`str`]
|
||||||
@param errors [list] if specified, store every errors in
|
:param modules: load backends which module is in list
|
||||||
@return [dict(str,BaseBackend)] return loaded backends
|
: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 = {}
|
loaded = {}
|
||||||
if storage is None:
|
if storage is None:
|
||||||
|
|
@ -195,6 +232,12 @@ class Weboob(object):
|
||||||
return loaded
|
return loaded
|
||||||
|
|
||||||
def unload_backends(self, names=None):
|
def unload_backends(self, names=None):
|
||||||
|
"""
|
||||||
|
Unload backends.
|
||||||
|
|
||||||
|
:param names: if specified, only unload that backends
|
||||||
|
:type names: :class:`list`
|
||||||
|
"""
|
||||||
unloaded = {}
|
unloaded = {}
|
||||||
if isinstance(names, basestring):
|
if isinstance(names, basestring):
|
||||||
names = [names]
|
names = [names]
|
||||||
|
|
@ -213,8 +256,11 @@ class Weboob(object):
|
||||||
"""
|
"""
|
||||||
Get a backend from its name.
|
Get a backend from its name.
|
||||||
|
|
||||||
It raises a KeyError if not found. If you set the 'default' parameter,
|
:param name: name of backend to get
|
||||||
the default value is returned instead.
|
: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:
|
try:
|
||||||
return self.backend_instances[name]
|
return self.backend_instances[name]
|
||||||
|
|
@ -225,6 +271,9 @@ class Weboob(object):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def count_backends(self):
|
def count_backends(self):
|
||||||
|
"""
|
||||||
|
Get number of loaded backends.
|
||||||
|
"""
|
||||||
return len(self.backend_instances)
|
return len(self.backend_instances)
|
||||||
|
|
||||||
def iter_backends(self, caps=None):
|
def iter_backends(self, caps=None):
|
||||||
|
|
@ -233,8 +282,9 @@ class Weboob(object):
|
||||||
|
|
||||||
Note: each backend is locked when it is returned.
|
Note: each backend is locked when it is returned.
|
||||||
|
|
||||||
@param caps Optional list of capabilities to select backends
|
:param caps: optional list of capabilities to select backends
|
||||||
@return iterator on selected 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()):
|
for name, backend in sorted(self.backend_instances.iteritems()):
|
||||||
if caps is None or backend.has_caps(caps):
|
if caps is None or backend.has_caps(caps):
|
||||||
|
|
@ -248,17 +298,21 @@ class Weboob(object):
|
||||||
|
|
||||||
This function has two modes:
|
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;
|
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
|
the locked backend instance at first arguments, and \*args and
|
||||||
\*\*kwargs.
|
\*\*kwargs.
|
||||||
|
|
||||||
@param function backend's method name, or callable object
|
:param function: backend's method name, or a callable object
|
||||||
@param backends list of backends to iterate on
|
:type function: :class:`str`
|
||||||
@param caps iterate on backends with this caps
|
:param backends: list of backends to iterate on
|
||||||
@param condition a condition to validate to keep the result
|
:type backends: list[:class:`str`]
|
||||||
@return the BackendsCall object (iterable)
|
: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 = self.backend_instances.values()
|
||||||
_backends = kwargs.pop('backends', None)
|
_backends = kwargs.pop('backends', None)
|
||||||
|
|
@ -296,16 +350,47 @@ class Weboob(object):
|
||||||
return BackendsCall(backends, condition, function, *args, **kwargs)
|
return BackendsCall(backends, condition, function, *args, **kwargs)
|
||||||
|
|
||||||
def schedule(self, interval, function, *args):
|
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)
|
return self.scheduler.schedule(interval, function, *args)
|
||||||
|
|
||||||
def repeat(self, 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)
|
return self.scheduler.repeat(interval, function, *args)
|
||||||
|
|
||||||
def cancel(self, ev):
|
def cancel(self, ev):
|
||||||
|
"""
|
||||||
|
Cancel an event
|
||||||
|
|
||||||
|
:param ev: the event identificator
|
||||||
|
"""
|
||||||
return self.scheduler.cancel(ev)
|
return self.scheduler.cancel(ev)
|
||||||
|
|
||||||
def want_stop(self):
|
def want_stop(self):
|
||||||
|
"""
|
||||||
|
Plan to stop the scheduler.
|
||||||
|
"""
|
||||||
return self.scheduler.want_stop()
|
return self.scheduler.want_stop()
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
|
"""
|
||||||
|
Run the scheduler loop
|
||||||
|
"""
|
||||||
return self.scheduler.run()
|
return self.scheduler.run()
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ from .formatters.load import FormattersLoader, FormatterLoadError
|
||||||
from .results import ResultsCondition, ResultsConditionError
|
from .results import ResultsCondition, ResultsConditionError
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['ReplApplication', 'NotEnoughArguments']
|
__all__ = ['NotEnoughArguments', 'ReplApplication']
|
||||||
|
|
||||||
|
|
||||||
class NotEnoughArguments(Exception):
|
class NotEnoughArguments(Exception):
|
||||||
|
|
@ -354,8 +354,9 @@ class ReplApplication(Cmd, ConsoleApplication):
|
||||||
def complete(self, text, state):
|
def complete(self, text, state):
|
||||||
"""
|
"""
|
||||||
Override of the Cmd.complete() method to:
|
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.
|
text already written by user.
|
||||||
"""
|
"""
|
||||||
super(ReplApplication, self).complete(text, state)
|
super(ReplApplication, self).complete(text, state)
|
||||||
|
|
|
||||||
|
|
@ -28,42 +28,103 @@ from weboob.tools.log import getLogger
|
||||||
from weboob.tools.value import ValuesDict
|
from weboob.tools.value import ValuesDict
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['BaseBackend', 'ObjectNotAvailable']
|
__all__ = ['ObjectNotAvailable', 'BackendStorage', 'BackendConfig', 'BaseBackend']
|
||||||
|
|
||||||
|
|
||||||
class ObjectNotAvailable(Exception):
|
class ObjectNotAvailable(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when an object is not available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class BackendStorage(object):
|
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):
|
def __init__(self, name, storage):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.storage = storage
|
self.storage = storage
|
||||||
|
|
||||||
def set(self, *args):
|
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:
|
if self.storage:
|
||||||
return self.storage.set('backends', self.name, *args)
|
return self.storage.set('backends', self.name, *args)
|
||||||
|
|
||||||
def delete(self, *args):
|
def delete(self, *args):
|
||||||
|
"""
|
||||||
|
Delete a value from the storage.
|
||||||
|
|
||||||
|
:param args: path to delete.
|
||||||
|
"""
|
||||||
if self.storage:
|
if self.storage:
|
||||||
return self.storage.delete('backends', self.name, *args)
|
return self.storage.delete('backends', self.name, *args)
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
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:
|
if self.storage:
|
||||||
return self.storage.get('backends', self.name, *args, **kwargs)
|
return self.storage.get('backends', self.name, *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
return kwargs.get('default', None)
|
return kwargs.get('default', None)
|
||||||
|
|
||||||
def load(self, default):
|
def load(self, default):
|
||||||
|
"""
|
||||||
|
Load storage.
|
||||||
|
|
||||||
|
:param default: this is the default tree if storage is empty
|
||||||
|
:type default: :class:`dict`
|
||||||
|
"""
|
||||||
if self.storage:
|
if self.storage:
|
||||||
return self.storage.load('backends', self.name, default)
|
return self.storage.load('backends', self.name, default)
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
"""
|
||||||
|
Save storage.
|
||||||
|
"""
|
||||||
if self.storage:
|
if self.storage:
|
||||||
return self.storage.save('backends', self.name)
|
return self.storage.save('backends', self.name)
|
||||||
|
|
||||||
|
|
||||||
class BackendConfig(ValuesDict):
|
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
|
modname = None
|
||||||
instname = None
|
instname = None
|
||||||
weboob = None
|
weboob = None
|
||||||
|
|
@ -72,12 +133,17 @@ class BackendConfig(ValuesDict):
|
||||||
"""
|
"""
|
||||||
Load configuration from dict to create an instance.
|
Load configuration from dict to create an instance.
|
||||||
|
|
||||||
@param weboob [Weboob] weboob object
|
:param weboob: weboob object
|
||||||
@param modname [str] name of module
|
:type weboob: :class:`weboob.core.ouiboube.Weboob`
|
||||||
@param instname [str] name of instance of this backend
|
:param modname: name of the module
|
||||||
@param params [dict] parameters to load
|
:type modname: :class:`str`
|
||||||
@param nofail [bool] if true, this call can't fail.
|
:param instname: name of this backend
|
||||||
@return [BackendConfig]
|
: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 = BackendConfig()
|
||||||
cfg.modname = modname
|
cfg.modname = modname
|
||||||
|
|
@ -103,12 +169,25 @@ class BackendConfig(ValuesDict):
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
|
"""
|
||||||
|
Dump config in a dictionary.
|
||||||
|
|
||||||
|
:rtype: :class:`dict`
|
||||||
|
"""
|
||||||
settings = {}
|
settings = {}
|
||||||
for name, value in self.iteritems():
|
for name, value in self.iteritems():
|
||||||
settings[name] = value.dump()
|
settings[name] = value.dump()
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
def save(self, edit=True, params=None):
|
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.modname is not None
|
||||||
assert self.instname is not None
|
assert self.instname is not None
|
||||||
assert self.weboob is not None
|
assert self.weboob is not None
|
||||||
|
|
@ -121,6 +200,22 @@ class BackendConfig(ValuesDict):
|
||||||
|
|
||||||
|
|
||||||
class BaseBackend(object):
|
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.
|
# Backend name.
|
||||||
NAME = None
|
NAME = None
|
||||||
# Name of the maintainer of this backend.
|
# Name of the maintainer of this backend.
|
||||||
|
|
@ -152,7 +247,9 @@ class BaseBackend(object):
|
||||||
OBJECTS = {}
|
OBJECTS = {}
|
||||||
|
|
||||||
class ConfigError(Exception):
|
class ConfigError(Exception):
|
||||||
pass
|
"""
|
||||||
|
Raised when the config can't be loaded.
|
||||||
|
"""
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
|
|
@ -184,16 +281,6 @@ class BaseBackend(object):
|
||||||
"""
|
"""
|
||||||
pass
|
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
|
_browser = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -202,7 +289,7 @@ class BaseBackend(object):
|
||||||
Attribute 'browser'. The browser is created at the first call
|
Attribute 'browser'. The browser is created at the first call
|
||||||
of this attribute, to avoid useless pages access.
|
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:
|
if self._browser is None:
|
||||||
self._browser = self.create_default_browser()
|
self._browser = self.create_default_browser()
|
||||||
|
|
@ -238,6 +325,11 @@ class BaseBackend(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def iter_caps(klass):
|
def iter_caps(klass):
|
||||||
|
"""
|
||||||
|
Iter capabilities implemented by this backend.
|
||||||
|
|
||||||
|
:rtype: iter[:class:`weboob.capabilities.base.IBaseCap`]
|
||||||
|
"""
|
||||||
def iter_caps(cls):
|
def iter_caps(cls):
|
||||||
for base in cls.__bases__:
|
for base in cls.__bases__:
|
||||||
if issubclass(base, IBaseCap) and base != IBaseCap:
|
if issubclass(base, IBaseCap) and base != IBaseCap:
|
||||||
|
|
@ -247,6 +339,9 @@ class BaseBackend(object):
|
||||||
return iter_caps(klass)
|
return iter_caps(klass)
|
||||||
|
|
||||||
def has_caps(self, *caps):
|
def has_caps(self, *caps):
|
||||||
|
"""
|
||||||
|
Check if this backend implements at least one of these capabilities.
|
||||||
|
"""
|
||||||
for c in caps:
|
for c in caps:
|
||||||
if (isinstance(c, basestring) and c in [cap.__name__ for cap in self.iter_caps()]) or \
|
if (isinstance(c, basestring) and c in [cap.__name__ for cap in self.iter_caps()]) or \
|
||||||
isinstance(self, c):
|
isinstance(self, c):
|
||||||
|
|
@ -255,7 +350,10 @@ class BaseBackend(object):
|
||||||
|
|
||||||
def fillobj(self, obj, fields=None):
|
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):
|
def not_loaded(v):
|
||||||
return (v is NotLoaded or isinstance(v, CapBaseObject) and not v.__iscomplete__())
|
return (v is NotLoaded or isinstance(v, CapBaseObject) and not v.__iscomplete__())
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,8 @@ else:
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['BrowserIncorrectPassword', 'BrowserBanned', 'BrowserUnavailable', 'BrowserRetry',
|
__all__ = ['BrowserIncorrectPassword', 'BrowserBanned', 'BrowserUnavailable', 'BrowserRetry',
|
||||||
'BrowserHTTPNotFound', 'BrowserHTTPError', 'BasePage', 'BaseBrowser', 'StandardBrowser']
|
'BrowserHTTPNotFound', 'BrowserHTTPError', 'BrokenPageError', 'BasePage',
|
||||||
|
'StandardBrowser', 'BaseBrowser']
|
||||||
|
|
||||||
|
|
||||||
# Exceptions
|
# Exceptions
|
||||||
|
|
@ -135,6 +136,21 @@ def check_location(func):
|
||||||
return inner
|
return inner
|
||||||
|
|
||||||
class StandardBrowser(mechanize.Browser):
|
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 --------------------------------------
|
# ------ Class attributes --------------------------------------
|
||||||
|
|
||||||
|
|
@ -161,16 +177,6 @@ class StandardBrowser(mechanize.Browser):
|
||||||
default_features.remove('_refresh')
|
default_features.remove('_refresh')
|
||||||
|
|
||||||
def __init__(self, firefox_cookies=None, parser=None, history=NoHistory(), proxy=None, logger=None, factory=None, responses_dirname=None):
|
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)
|
mechanize.Browser.__init__(self, history=history, factory=factory)
|
||||||
self.logger = getLogger('browser', logger)
|
self.logger = getLogger('browser', logger)
|
||||||
|
|
||||||
|
|
@ -309,15 +315,16 @@ class StandardBrowser(mechanize.Browser):
|
||||||
def buildurl(base, *args, **kwargs):
|
def buildurl(base, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Build an URL and escape arguments.
|
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:
|
Example:
|
||||||
|
|
||||||
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
|
>>> buildurl('/blah.php', ('a', '&'), ('b', '=')
|
||||||
'/blah.php?a=%26&b=%3D'
|
'/blah.php?a=%26&b=%3D'
|
||||||
>>> buildurl('/blah.php', a='&', 'b'='=')
|
>>> buildurl('/blah.php', a='&', 'b'='=')
|
||||||
'/blah.php?b=%3D&a=%26'
|
'/blah.php?b=%3D&a=%26'
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not args:
|
if not args:
|
||||||
|
|
@ -336,11 +343,16 @@ class StandardBrowser(mechanize.Browser):
|
||||||
"""
|
"""
|
||||||
Set a value to a form field.
|
Set a value to a form field.
|
||||||
|
|
||||||
@param args [dict] arguments where to look for value.
|
:param args: arguments where to look for value
|
||||||
@param label [str] label in args.
|
:type args: dict
|
||||||
@param field [str] field name. If None, use label instead.
|
:param label: label in args
|
||||||
@param value [str] value to give on field.
|
:type label: str
|
||||||
@param is_list [bool] the field is a list.
|
: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:
|
try:
|
||||||
if not field:
|
if not field:
|
||||||
|
|
@ -366,6 +378,29 @@ class StandardBrowser(mechanize.Browser):
|
||||||
class BaseBrowser(StandardBrowser):
|
class BaseBrowser(StandardBrowser):
|
||||||
"""
|
"""
|
||||||
Base browser class to navigate on a website.
|
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 --------------------------------------
|
# ------ Class attributes --------------------------------------
|
||||||
|
|
@ -406,20 +441,6 @@ class BaseBrowser(StandardBrowser):
|
||||||
def __init__(self, username=None, password=None, firefox_cookies=None,
|
def __init__(self, username=None, password=None, firefox_cookies=None,
|
||||||
parser=None, history=NoHistory(), proxy=None, logger=None,
|
parser=None, history=NoHistory(), proxy=None, logger=None,
|
||||||
factory=None, get_home=True, responses_dirname=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)
|
StandardBrowser.__init__(self, firefox_cookies, parser, history, proxy, logger, factory, responses_dirname)
|
||||||
self.page = None
|
self.page = None
|
||||||
self.last_update = 0.0
|
self.last_update = 0.0
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ __all__ = ['FrenchTransaction']
|
||||||
|
|
||||||
|
|
||||||
class FrenchTransaction(Transaction):
|
class FrenchTransaction(Transaction):
|
||||||
|
"""
|
||||||
|
Transaction with some helpers for french bank websites.
|
||||||
|
"""
|
||||||
PATTERNS = []
|
PATTERNS = []
|
||||||
|
|
||||||
def clean_amount(self, text):
|
def clean_amount(self, text):
|
||||||
|
|
@ -61,7 +64,7 @@ class FrenchTransaction(Transaction):
|
||||||
|
|
||||||
When calling this method, you should have defined patterns (in the
|
When calling this method, you should have defined patterns (in the
|
||||||
PATTERN class attribute) with a list containing tuples of regexp
|
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),
|
PATTERNS = [(re.compile('^VIR(EMENT)? (?P<text>.*)'), FrenchTransaction.TYPE_TRANSFER),
|
||||||
(re.compile('^PRLV (?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
|
(re.compile('^PRLV (?P<text>.*)'), FrenchTransaction.TYPE_ORDER),
|
||||||
|
|
@ -70,8 +73,9 @@ class FrenchTransaction(Transaction):
|
||||||
]
|
]
|
||||||
|
|
||||||
In regexps, you can define this patterns:
|
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 not isinstance(date, (datetime.date, datetime.datetime)):
|
||||||
if date.isdigit() and len(date) == 8:
|
if date.isdigit() and len(date) == 8:
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ from weboob.tools.newsfeed import Newsfeed
|
||||||
|
|
||||||
|
|
||||||
class GenericNewspaperBackend(BaseBackend, ICapMessages):
|
class GenericNewspaperBackend(BaseBackend, ICapMessages):
|
||||||
"GenericNewspaperBackend class"
|
"""
|
||||||
|
GenericNewspaperBackend class
|
||||||
|
"""
|
||||||
MAINTAINER = 'Julien Hebert'
|
MAINTAINER = 'Julien Hebert'
|
||||||
EMAIL = 'juke@free.fr'
|
EMAIL = 'juke@free.fr'
|
||||||
VERSION = '0.c'
|
VERSION = '0.c'
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,23 @@
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
# 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):
|
class Thumbnail(CapBaseObject):
|
||||||
|
"""
|
||||||
|
Thumbnail of an image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
url = StringField('URL to photo thumbnail')
|
||||||
|
data = BytesField('Data')
|
||||||
|
|
||||||
def __init__(self, url):
|
def __init__(self, url):
|
||||||
CapBaseObject.__init__(self, url)
|
CapBaseObject.__init__(self, url)
|
||||||
self.add_field('url', basestring, url.replace(' ', '%20'))
|
self.url = url.replace(' ', '%20')
|
||||||
self.add_field('data', str)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
|
||||||
|
|
@ -60,14 +60,18 @@ class LxmlHtmlParser(IParser):
|
||||||
"""
|
"""
|
||||||
Select one or many elements from an element, using lxml cssselect by default.
|
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 element: element on which to apply selector
|
||||||
@param selector [str] CSS or XPath expression
|
:type element: object
|
||||||
@param method [str] (cssselect|xpath)
|
:param selector: CSS or XPath expression
|
||||||
@param nb [int] number of elements expected to be found.
|
:type selector: str
|
||||||
Use None for undefined number, and 'many' for 1 to infinite.
|
:param method: (cssselect|xpath)
|
||||||
@return one or many Element
|
: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':
|
if method == 'cssselect':
|
||||||
results = element.cssselect(selector)
|
results = element.cssselect(selector)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue